storkCore

Check-in [348eb670aa]
Login

Check-in [348eb670aa]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Some small documentation changes + merge
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 348eb670aa820487d05f0dff31f12781a36ff50c
User & Date: setok 2015-01-11 01:11:30.504
Context
2015-01-26
22:13
Some cleanup check-in: c616166f49 user: setok tags: trunk
2015-01-11
01:11
Some small documentation changes + merge check-in: 348eb670aa user: setok tags: trunk
2015-01-09
12:56
Small change in comment Leaf check-in: 889d0e8937 user: setok tags: holvilatest
2013-09-25
14:28
Basic gitignore check-in: 1ab36853fd user: setok@scred.com tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to formHandler.js.







1













































































































































































































































































































































































































































































































































































































































































































































2

3

4
5
6
7
8

9

10

11

12
13
14







var FormController = clone(Stork);

















































































































































































































































































































































































































































































































































































































































































































































FormController.addField = function(fieldElement) {
    var my = this;

    fieldElement.onfocus = function() {
	my.onFocus(fieldElement);

    }

}



FormController.onFocus = function(element) {
    return true;
}
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
|
>


|
|
|
>

>
|
>
|
>


|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
/**
 * The form controller
 *
 * @class StorkUtil.FormController
 * @extends StorkCore.StorkController
 * @singleton
 */
var FormController = clone(StorkController);

/**
 * @property id
 *
 * identifier for the form
 */
FormController.copiedProperty("id", undefined);

/**
 * @property {Array} listeners
 *
 *  list of listeners attached to this form
 */
FormController.copiedProperty("listeners", []);

/**
 * @property {String} errorItemTemplate
 *
 * identifier for the HTML element that will be used as template to add errors
 * to the error containers
 */
FormController.copiedProperty("errorItemTemplate", undefined);

/**
 * @property {Boolean} ignoreErrorContainers
 * true to ignore identifying the error containers
 */
FormController.copiedProperty("ignoreErrorContainers", false);

/**
 * @property {Object} acceptedElementList
 *
 * Map of the types of elements which this can handle as form elements.
 */
FormController.acceptedElementList = {
    input: true,
    button: true,
    select: true,
    textarea: true
};


/**
 * @property {Object} ignoreFields
 *
 * These fields will not be serialised.
 */
FormController.ignoredFields = {

};

/**
 * @property {Object} typeFilter
 *      map of field types and methods for validating value and setting and
 *      getting value
 */
FormController.typeFilter = {
    input: {
        checker: function(element) {
            return (FormController._attrEqualsLowerCase(element, "type",
                                                        "checkbox") ||
                    FormController._attrEqualsLowerCase(element, "type",
                                                        "radio") ||
                    FormController._attrEqualsLowerCase(element, "type",
                                                        "submit") ||
                    FormController._attrEqualsLowerCase(element, "type",
                                                        "button"));
        },
        hasValidValue: function(element) {
            return element.checked;
        },
        setValue: function(element, value) {
            if(FormController._attrEqualsLowerCase(element, "type", "submit")) {
                return undefined;
            }
            var valueSet = false;
            if(typeof value === "array") {
                for(var i=0; i < value.length; i++) {
                    if(element.value == value[i]) {
                        element.checked = true;
                        valueSet = true;
                    }
                }
            }
            if(element.value == value) {
                element.checked = true;
                valueSet = true;
            }
            if(valueSet == false) {
                element.checked = false;
            }
            return undefined;
        }
    },
    select: {
        checker: function() {return true;},
        hasValidValue: function(element) {
            return (element.selectedIndex !== undefined &&
                    element.selectedIndex !== null &&
                    element.selectedIndex !== -1);
        },
        setValue: function(element, value) {
            if (!value){
                element.value="";
                return;
            }
            var options = element.options;
            for(var i=0, len=options.length; i < len; i++) {
                if(options[i].value === value) {
                    element.selectedIndex = i;
                    return;
                }
            }
        },
        value: function(element) {
            var value = undefined;
            if(!(element.selectedIndex < 0)) {
                value = element.options[element.selectedIndex].value;
            }
            return value;
        }
    },
    textarea: {
        checker: function() {return true;},
        setValue: function(element, value) {
            emptyElement(element);
            // browser compatibility: reset the value of the textarea
            if (element.value){
                element.value="";
            }

            if(value) {
                element.appendChild(document.createTextNode(value));
                try {
                    element.value = value;
                }
                catch(e) {
                }
            }
        },
        hasValidValue: function(element) { return true; },
        value: function(element) {
            return element.value;
        }
    }

};

/**
 * @property {Object} childrenDecideValue
 *      map of field types for which their children decide the value, for
 *      example `<option>` defines the value of `<select>`
 */
FormController.childrenDecideValue = {
    select: true,
    optgroup: true
};


//**** Private methods ****

/**
 * @protected
 */
FormController._attrEqualsLowerCase = function(element, name, string) {
    return element.getAttribute(name) ? element.getAttribute(name).toLowerCase() == string : false;
};

/**
 * @protected
 * Returns the value of an element based in the dom value attribute or the
 * node attribute via getAttribute
 *
 * @param {HTMLElement} element element to extract the value from
 * @return {Mixed} returns the value for the element or undefined
 */
FormController._valueFromAttributeOrValue = function(element) {
    return element.value || element.getAttribute("value") || undefined;
};

/**
 * @protected
 * Sets the value for a field. Sets first via setAttribute dom method and then
 * tries to set it using the `value` javascript dom attribute.
 *
 * @param {HTMLElement} element html element for the field
 * @param {Mixed} value value to set for the field
 */
FormController._setVal = function(element, value) {
    if(value == undefined) {
        value = "";
    }
    element.setAttribute("value", value);
    try {
        element.value = value;
    }
    catch(e) {
    }
};

/**
 * @protected
 *
 * Verifies that the element for a form is correct before allowing it to be
 * attached to the controller
 *
 * @param {HTMLElement} element html element for the form
 * @return {Mixed} undefined if the form is correct or a list of errors if
 *      there was any error when validating the form element
 */
FormController._checkFormElement = function(element) {
    var error = undefined;
    if(!element ||
       !this._attrEqualsLowerCase(element, "tagname", "form")) {
        error = "No form element is attached.";
    }
    if(element.elements) {
        error = this._checkErrorContainers(element);
    }
    return error;
};

/**
 * @protected
 *
 * Checks that the error containers exist for every HTML element this method
 * uses the {@link _getElementErrorContainerId} and the field element id to resolve
 * the id for the error container for the field
 *
 * @param {HTMLElement} element dom node for the field to find the error
 *      container for
 * @return {Mixed} undefined if the error container is found or an error
 *      message if there was any error when validating the form element
 */
FormController._checkErrorContainers = function(element) {
    if(this.ignoreErrorContainers == true) return undefined;
    var error = undefined;
    var id = element.getAttribute("id");
    for(var i=0; i<element.elements.length; i++) {
        var el = element.elements[i];
        var name = el.getAttribute("name");
        if(!name || el.getAttribute("type") === "hidden") {
            continue;
        }
        var containerId = this._getElementErrorContainerId(id, name);
        var errorContainer = document.getElementById(containerId);
        if(!errorContainer) {
            if(!error) {
                error = "";
            }
            if(error) {
                error += "\r\n";
            }
            error += "Error container for " + el.getAttribute("name") + " in form " + id +" does not exist.";
        }
    }
    return error;
};

/**
 * @protected
 *
 * Returns the id for the error container for a field based on its name and the
 * form name
 *
 * @param {String} formId id for the form containing the field to find the error
 *      container for.
 * @param {String} name name for the the field to find the error container for.
 * @return {String} id for the error container
 */
FormController._getElementErrorContainerId = function(formId, name) {
    return this._getIdPrefix(formId, name) + "-errorContainer";
};

/**
 * @protected
 * Returns the id for the list of errors base element for a field based on its
 * name and the form name
 *
 * @param {String} formId id for the form containing the field to find the list
 *      of errors base element for.
 * @param {String} name name for the the field to find the list of errors base
 *      element for.
 * @return {String} id for the list of errors base element for.
 */
FormController._getElementErrorListId = function(formId, name) {
    return this._getIdPrefix(formId, name) + "-errorList";
};

/**
 * @protected
 *
 * Returns the prefix id for an element part of form based on the element name
 * and the form name
 *
 * @param {String} formId id for the form containing the field to get the
 *        element prefix id for.
 * @param {String} name name for the the field to get the element prefix id for.
 * @return {String} id for the element
 */
FormController._getIdPrefix = function(formId, name) {
    return formId + "-" + name;
};


/**
 * @protected
 */
FormController._applyToChildren = function(form, apply, matcher, values) {
    for(var i=0, l=form.length; i < l; i++) {
        if(matcher.call(this, form[i])) {
            values = apply.call(this, form[i], i, values);
        }
    }
    return values;
};

/**
 * @protected
 */
FormController._digValueFromElement = function(element) {
    var value = {};
    value.name = element.getAttribute("name");
    value.value = undefined;
    var typeFilter = this._typeFilter(element);
    if(typeFilter) {
        value.value = this._getValueWithTypeFilter(typeFilter, element, this._valueFromAttributeOrValue);
    }
    else {
        value.value = this._valueFromAttributeOrValue(element);
    }
    return value;
};

/**
 * @protected
 */
FormController._typeFilter = function(element) {
    return this.typeFilter[element.tagName.toLowerCase()];
};

/**
 * @protected
 */
FormController._getValueWithTypeFilter = function(typeFilter, element, defaultValueFunction) {
    var value = undefined;
    if(typeFilter.checker.call(this, element)) {
        if(typeFilter.hasValidValue.call(this, element)) {
            value = typeFilter.value ? typeFilter.value.call(this, element) : defaultValueFunction.call(this, element);
        }
    }
    else {
        value = defaultValueFunction.call(this, element);
    }
    return value;
};

/**
 * @protected
 */
FormController._setValueWithTypeFilter = function(typeFilter, element, value, defaultSetFunction) {
    if(typeFilter.checker.call(this, element)) {
        value = typeFilter.setValue ? typeFilter.setValue.call(this, element, value) : defaultSetFunction.call(this, element, value);
    }
    else {
        defaultSetFunction.call(this, element, value);
    }
    return value;
};

/**
 * @protected
 */
FormController._contains = function(object, key) {
    return object[key.toLowerCase()];
};


/**
 * Checks to see if the given element type is a valid element for
 * serialisation.
 *
 * @protected
 */
FormController._isFormElement = function(elementType) {
    return this._contains(this.acceptedElementList, elementType);
};

/**
 * @protected
 */
FormController._insertToValues = function(value, values) {
    if(!value.name || !value.value)
        return values;
    if(values[value.name]) {
        if(values[value.name].push) {
            values[value.name].push(value.value);
        }
        else {
            var existingValue = values[value.name];
            values[value.name] = [existingValue, value.value];
        }
    }
    else {
        values[value.name] = value.value;
    }
    return values;
};


/**
 * Set the "value" attribute of 'element' to a matching value from 'values'.
 * The match is based on finding the key in 'values' that matches the "name"
 * attribute of 'element.
 *
 * @protected
 */
FormController._setValue = function(element, values) {
    var elementName = element.getAttribute("name");
    var typeFilter = this._typeFilter(element);
    if(!values) {
        values = {};
    }
    if(typeFilter) {
        this._setValueWithTypeFilter(typeFilter, element, values[elementName], FormController._setVal);
    }
    else {
        this._setVal(element, values[elementName]);
    }
};


/**
 * @protected
 */
FormController._useElementInForm = function(element) {
    if (! this._isFormElement(element.tagName)) {
        return false;
    } else {
        return !this.isIgnoredField(element.name);
    }
};


//**** Public methods ****


/**
 * Specifies that the field with the name 'fieldName' should not be
 * serialised in the serialize() method. Can be useful for fields which
 * are used for other purposes (probably JS-controlled), but which are
 * not relevant for the actual form data.
 */
FormController.ignoreField = function(fieldName) {
    this.ignoredFields[fieldName] = true;
};


/**
 *
 */
FormController.isIgnoredField = function(fieldName) {
    return this.ignoredFields.hasOwnProperty(fieldName);
};


/**
 * setErrorTemplate
 * Set the error template you're using. It has to be set before you try
 * to use error method.
 * @param {string/HTMLElement} element to be used as error template
 */
FormController.setErrorTemplate = function(element) {
    if(isString(element)) {
        element = elementByID(element);
    }
    this.errorItemTemplate = element;
};

//overrides superlass
FormController.attach = function(element) {
    StorkController.attach.apply(this, [element]);
    element = isString(element) ? elementByID(element) : element;
    this.myElement = element;
    this.id = element.getAttribute("id");

    var error = this._checkFormElement(element);
    if(error) {
        throw error;
    }
    var my = this;
    this.wrapper = function(event) {
        if (!event) event = window.event;
        if (event.preventDefault) event.preventDefault();
        event.returnValue = false;
        var serialized = my.serialize();
        my.informListeners("receiveSerialized",
                             [my, simpleClone(serialized)]);
    };
    bean.add(this.getElement(), "submit", this.wrapper);
    return this;
};

/**
 *
 */
FormController.disable = function() {
    var error = this._checkFormElement(this.myElement);
    if(error) {
        throw error;
    }
    var elements = this.myElement.elements;
    for(var i=0, l=elements.length; i<l; i++) {
        elements[i].setAttribute("disabled", true);
    }
    this.superMethod(FormController.disable, "disable");
};

/**
 *
 */
FormController.enable = function() {
    var error = this._checkFormElement(this.myElement);
    if(error) {
        throw error;
    }
    var elements = this.myElement.elements;
    for(var i=0, l=elements.length; i<l; i++) {
        elements[i].removeAttribute("disabled");
    }
    this.superMethod(FormController.enable, "enable");
};


/* overrides method in superclass
 *
 * Detach controller from currently attached element.
 *
 * @return {HTMLElement}
 */
FormController.detach = function() {
    if(!this.getElement()) {
        return undefined;
    }
    var element = this.getElement();
    this.myElement = undefined;
    delete this.myElement;
    bean.remove(element, "submit", this.wrapper, false);
    this.formElements = undefined;
    return element;
};


/**
 * Resets the form with given values or to empty if no values were given
 * Each key from 'values' will be matched with an element from this form
 * with the "name" element matching that key. The value of that key will
 * be set to the "value" attribute in the matching element.
 *
 * @param values that the values should be reset to as a dict.
 */
FormController.reset = function(values) {
    this.informListeners('beforeFormReset');

    var element = this.myElement;
    var error = this._checkFormElement(element);
    if(error) {
        throw error;
    }
    var applyTo = function(child, index, vals) {
        var value = this._setValue(child, vals);
        this.removeErrors(child.getAttribute("name"));
        return vals;
    };
    this._applyToChildren(element,
                          applyTo,
                          this._useElementInForm,
                          values);
    this.informListeners('afterFormReset');
};

/**
 * Tries to find the value of form field with given name.
 *
 * @param {String} name of the form field
 * @return {Object} value of the field
 */
FormController.value = function(name) {
    var value = undefined;
    var error = this._checkFormElement(this.myElement);
    if(error) {
        throw error;
    }
    if(this.myElement[name]) {
        value = this._digValueFromElement(this.myElement[name])["value"];
    }
    return value;
};

/**
 * Serializes the form to an object.
 *
 * @param element which to serialize, or if not given
 * attached element is serialized.
 *
 * @return an object with keys 'url' (matching form action) and
 * 'method' (matching form method), with 'values' being a key-value
 * set of all the form fields.
 */
FormController.serialize = function(form) {
    if(form === undefined) {
        form = this.myElement;
    }
    var error = this._checkFormElement(form);
    if(error) {
        throw error;
    }
    var data = {}, values = {};
    data.url = form.getAttribute("action");
    data.method = form.getAttribute("method");
    var applyTo = function(child, index, values) {
        return this._insertToValues(this._digValueFromElement(child),
                                    values);
    };

    data.values = this._applyToChildren(form,
                                        applyTo,
                                        this._useElementInForm,
                                        values);
    return data;
};

/**
 * Add errors for a specific form field.
 * @param {String} name of the form field
 * @param {HTMLElement} HTML element containing the error list
 */
FormController.addErrors = function(name, errors) {
    var error = this._checkFormElement(this.myElement);
    if(error) {
        throw error;
    }
    if(!this.myElement && !this.myElement[name] &&
       this.myElement[name].getAttribute("type") !== "hidden") {
        return;
    }

    var element = this.myElement[name];
    addClass(element, "error");
    var errorContainerId = this._getElementErrorContainerId(this.id, name);
    var errorContainer = elementByID(errorContainerId);
    addClass(errorContainer, "visible");

    var errorListId = this._getElementErrorListId(this.id, name);
    var errorList = elementByID(errorListId);
    emptyElement(errorList);
    for(var i=0; i<errors.length; i++) {
        var err = this.errorItemTemplate.cloneNode(true);
        err.removeAttribute("id");
        setTextOfElement(err, errors[i]);
        errorList.appendChild(err);
    }
};

/**
 * Remove errors for a specific form field with it's name.
 *
 * @param {String} name of the form field
 */
FormController.removeErrors = function(name) {
    if(!this.myElement.elements[name] ||
       this.myElement[name].getAttribute("type") === "hidden") {
        return;
    }
    var element = this.myElement.elements[name];
    if(isArray(element)) {
        throw "Form has more than one element with the name of " + name +
            " which means that it's impossible to know the error container ID.";
    }
    removeClass(element, "error");
    var errorContainerId = this._getElementErrorContainerId(this.id, name);
    var errorContainer = elementByID(errorContainerId);
    removeClass(errorContainer, "visible");
    emptyElement(errorContainer.getElementsByTagName("ul")[0]);
};

/**
 * By convention if you want to use error function, you should
 * have a container for every form element with id
 * {form-element-id}-{field-name}-error
 *
 * @param {Object} errors
 *      map with the field name as key and message as error
 */
FormController.error = function(errors) {
    if(!errors) {
        return;
    }
    this.clearErrors();
    for(var error in errors) {
        this.addErrors(error, errors[error]);
    }
};

/**
 *
 */
FormController.clearErrors = function() {
    if(this.myElement) {
        var elements = this.myElement.elements;
        for(var i=0, l=elements.length; i<l; i++) {
            if(elements[i].name) {
                this.removeErrors(elements[i].name);
            }
        }
    }
};

/**
 *
 */
FormController.addField = function(fieldElement) {
    var my = this;
    if(fieldElement) {
        fieldElement.onfocus = function() {
            my.onFocus(fieldElement);
        };
    }
};

/**
 *
 */
FormController.onFocus = function(element) {
    return true;
};
Changes to list.js.
124
125
126
127
128
129
130



131
132
133
134
135
136
137
        itemController.setListID(this.listID);
        itemController.setListItemID(this.nextItemID);
        itemController.setListModel(listModel);
        this.nextItemID++;
        itemController.createElement(document, this.containerElement);
        itemController.drawed();
        this.itemControllers[i] = itemController;



    }

    listModel.addListener(this);
};


/**







>
>
>







124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        itemController.setListID(this.listID);
        itemController.setListItemID(this.nextItemID);
        itemController.setListModel(listModel);
        this.nextItemID++;
        itemController.createElement(document, this.containerElement);
        itemController.drawed();
        this.itemControllers[i] = itemController;
        this.informListeners('listItemControllerAdded',[this,
                                                        itemController,
                                                        i]);
    }

    listModel.addListener(this);
};


/**
191
192
193
194
195
196
197


198
199
200
201
202
203
204
};

ListController.deleteItemEvent = function(source, item, index) {
    var itemController = this.itemControllers[index];
    if(itemController) {
        this.itemControllers.splice(index, 1);
        itemController.deleted();


        this.showOrHideZeroLengthElement();
    }
};


/**
 * React to 'insertBefore' event coming from model.







>
>







194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
};

ListController.deleteItemEvent = function(source, item, index) {
    var itemController = this.itemControllers[index];
    if(itemController) {
        this.itemControllers.splice(index, 1);
        itemController.deleted();
        this.informListeners('listItemControllerDeleted',
                             [this, itemController]);
        this.showOrHideZeroLengthElement();
    }
};


/**
 * React to 'insertBefore' event coming from model.
234
235
236
237
238
239
240




241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
        /*console.log(this.containerElement);*/
        itemController.createElement(document, this.containerElement);
        this.itemControllers.push(itemController);
    }

    itemController.setListModel(this.model);
    itemController.drawed();




    /*console.log("insertBeforeEvent - itemControllers.length: " +
     this.itemControllers.length);*/

    this.showOrHideZeroLengthElement();
};


/**
 * Controller for list items contained within lists
 *
 * @class StorkUtil.ListItemController
 * @extends StorkCore.StorkController
 * @singleton
 */
var ListItemController = clone(StorkController);

/**
 * @property itemTemplate
 */
ListItemController.itemTemplate = undefined;
/**







>
>
>
>







|
|
|
<
<
<
<







239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259




260
261
262
263
264
265
266
        /*console.log(this.containerElement);*/
        itemController.createElement(document, this.containerElement);
        this.itemControllers.push(itemController);
    }

    itemController.setListModel(this.model);
    itemController.drawed();

    this.informListeners('listItemControllerAdded',[this,
                                                    itemController,
                                                    index]);
    /*console.log("insertBeforeEvent - itemControllers.length: " +
     this.itemControllers.length);*/

    this.showOrHideZeroLengthElement();
};


/*****************************************************************************
 * Controller for items contained within lists
 *****************************************************************************/




var ListItemController = clone(StorkController);

/**
 * @property itemTemplate
 */
ListItemController.itemTemplate = undefined;
/**
458
459
460
461
462
463
464
465
466
467
468
469
470



471
472


473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
        attrName: attrName,
        propertyMap: propertyMap
    };
};


/**
 * Link changes in 'modelProperty' to a method, that should be set in a clone
 * of this ListItemController.
 *
 * 'elementIDs', if set, is a list of IDs for elements that should be passed
 *               to the method called. In the template (set with
 *               setItemTemplate()) the IDs should be of the form <id>_#n.



 *               The actual elements will be looked up and passed to the method
 *               in its 'elements' argument.


 *
 *               If this is not defiend, 'elements' will not be passed to the
 *               method.
 *
 * The method will be called with either one or two arguments:
 * 'modelProperty' and optionally 'elements', as specified above.
 *
 */
ListItemController.linkPropertyToMethod = function(modelProperty, methodName,
                                                   elementIDs) {

    this.propertyMethods[modelProperty] = {
        idPrefixes: elementIDs,
        method: methodName
    };
};









|
|

|


>
>
>

|
>
>

|








<







463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492

493
494
495
496
497
498
499
        attrName: attrName,
        propertyMap: propertyMap
    };
};


/**
 * Link changes in 'modelProperty' to a method, that should exist in this
 * ListItemController.
 *
 * 'elementIDs', if set, is an array of IDs for elements that should be passed
 *               to the method called. In the template (set with
 *               setItemTemplate()) the IDs should be of the form <id>_#n.
 *               In this array the IDs should be given without the "_#n" 
 *               suffix.
 *
 *               The actual elements will be looked up and passed to the method
 *               in its 'elements' argument as a dictionary with the given
 *               ID as the key, and the element as a value, such as:
 *               {givenID: <element>, anotherID: <anotherElement>}
 *
 *               If this is not defined, 'elements' will not be passed to the
 *               method.
 *
 * The method will be called with either one or two arguments:
 * 'modelProperty' and optionally 'elements', as specified above.
 *
 */
ListItemController.linkPropertyToMethod = function(modelProperty, methodName,
                                                   elementIDs) {

    this.propertyMethods[modelProperty] = {
        idPrefixes: elementIDs,
        method: methodName
    };
};


976
977
978
979
980
981
982































































































































































/**
 * Get the item model for the given 'itemNumber' (an index into the item list).
 */
ListModel.getItemModel = function(itemNumber) {
    return this.items[itemNumber];
};





































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149

/**
 * Get the item model for the given 'itemNumber' (an index into the item list).
 */
ListModel.getItemModel = function(itemNumber) {
    return this.items[itemNumber];
};

/** ***********************************************************************
 * implements a list with filtering of its items
 **************************************************************************/
var FilteredListModel = clone(ListModel);

/**
 * abstract method to determine if an item must be included. The method returns
 * true to include item (in same fashion as django does).
 *
 * @param {Object} item the item to evaluate
 * @return {Boolean} true if the item should be included
 */
FilteredListModel._applyFilter = function(item){
    return this.filterFn(item);
};

FilteredListModel.filterFn = function(item){
    return true;
}

/**
 * takes all the items from a list model and applies filter over them. Makes
 * the items for the filtered list the ones that match the filter
 * function.
 *
 * @param {ListModel} listModel list model to filter
 */
FilteredListModel._setItemsFromListModel = function(listModel){
    var i, len, item;
    // make the filtering
    this.items = [];
    for (i=0,len=listModel.getLength(); i<len; i++){
        item = listModel.getItemModel(i);
        if (this._applyFilter(item)===true){
            this._pushObject(item);
        }
    }
    this.informListeners("resetEvent", [this]);
};

/**
 * sets the list model to filter
 */
FilteredListModel.setListModel = function(listModel){
    var me = this;

    if (this._listModel){
        this.unsetListModel();
    }

    this._listModel = listModel;
    this._setItemsFromListModel(listModel);

    this._listModelListeners = {
        resetEvent:function(listModel){
            me._setItemsFromListModel(listModel);
        },
        insertBeforeEvent: function(listModel, model, index){
            var lastItem=null, newIndex, i, len;
            if (me._applyFilter(model)===true){
                // find the position in the filtered array

                // if the model is added at the end
                if (index == (listModel.getLength() - 1)){
                    me._pushObject(model);
                    return;
                }else if (index <= 0){
                    // if the index points to the first position,
                    // add it at the beginning
                    me._insertObjectBefore(model, 0);
                    return;
                }

                newIndex = index - 1;
                lastItem = listModel.getItemModel(newIndex);
                while (me._applyFilter(lastItem)!==true && newIndex>0){
                    newIndex--;
                    lastItem = listModel.getItemModel(newIndex);
                }

                // if the index points to the first position,
                // add it at the beginning
                if (newIndex === 0){
                    me._insertObjectBefore(model, 0);
                    return;
                }

                // finally find the index in the current array for the found
                // item and add it after it (i + 1).
                for (i=0, len=me.items.length; i<len; i++){
                    if (lastItem == me.items[i]){
                        me._insertObjectBefore(model, i+1);
                        break;
                    }
                }
            }
        },
        deleteItemEvent: function(listModel, model, index){
            var i,len;
            for (i=0, len=me.items.length; i<len; i++){
                if (model == me.items[i]){
                    me._deleteIndex(i);
                    break;
                }
            }
        }
    };
    this._listModel.addListener(this._listModelListeners);
};

FilteredListModel.unsetListModel = function(){
    if (this._listModel){
        this._listModel.removeListener(this._listModelListeners);
        this._listModel = null;
    }
};


//override
FilteredListModel._insertObjectBefore = function(model, index) {
    this.items.splice(index, 0, model);
    this.informListeners("insertBeforeEvent", [this, model, index]);
    return model;
};

//override
FilteredListModel._pushObject = function(model) {
    this.items.push(model);
    this.informListeners("insertBeforeEvent",
                         [this, model, this.items.length-1]);
    return model;
};

FilteredListModel._deleteIndex = FilteredListModel.deleteIndex;

FilteredListModel.throwReadOnlyError = function(){
    throw Error("Method not available in read only FilteredListModel instance");
};
FilteredListModel.pushObject = FilteredListModel.throwReadOnlyError;
FilteredListModel.setItemsFromArray = FilteredListModel.throwReadOnlyError;
FilteredListModel.insertObjectBefore = FilteredListModel.throwReadOnlyError;
FilteredListModel.deleteItem = FilteredListModel.throwReadOnlyError;
FilteredListModel.deleteIndex = FilteredListModel.throwReadOnlyError;


/** ***********************************************************************
 * implements a list with exclusion of its items
 **************************************************************************/
var ExcludedListModel = clone(FilteredListModel);

ExcludedListModel._applyFilter = function(item){
    return !this.excludeFn(item);
};

ExcludedListModel.excludeFn = function(item){
    return true;
};
Changes to storkCore.js.
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
            }
        }
        return false;
    };
})();

function logError(error) {
    if(console && console.error) {
        console.error(error);
    }
}

/**
 * Similar to document.getElementByID, but throws an error if not found.
 */








|
|







235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
            }
        }
        return false;
    };
})();

function logError(error) {
    if(window.console && window.console.error) {
        window.console.error(error);
    }
}

/**
 * Similar to document.getElementByID, but throws an error if not found.
 */

380
381
382
383
384
385
386
387
388
389
390
391
392
393
394

/** Add 'className' to class definition of 'element'. **/

function addClass(element, className) {
    if(isString(element)) {
        element = elementByID(element);
    }
    if (element && className && (!this.hasClass(element, className)) ) {
        element.className += " " + className;
    }
}


/** Remove the 'class' from the class definition of 'element. **/








|







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394

/** Add 'className' to class definition of 'element'. **/

function addClass(element, className) {
    if(isString(element)) {
        element = elementByID(element);
    }
    if (element && className && (!hasClass(element, className)) ) {
        element.className += " " + className;
    }
}


/** Remove the 'class' from the class definition of 'element. **/

410
411
412
413
414
415
416







417
418
419
420
421
422
423
}

function changeClassFromTo(element, from, to) {
    removeClass(element, from);
    addClass(element, to);
    return element;
}








/**
 * Clone a node and update its ID and its children's IDs
 * by assigning 'index' as a number for
 * each.
 *
 * 'Prefix' will be a string attached to the front of each changed ID.







>
>
>
>
>
>
>







410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
}

function changeClassFromTo(element, from, to) {
    removeClass(element, from);
    addClass(element, to);
    return element;
}

/**
 * Adds a class if the add parameter is true if not removes it
 */
function yesNoClass(element, className, add){
    ((add === true)?addClass:removeClass).call(this, element, className)
}

/**
 * Clone a node and update its ID and its children's IDs
 * by assigning 'index' as a number for
 * each.
 *
 * 'Prefix' will be a string attached to the front of each changed ID.
456
457
458
459
460
461
462

463

464
465
466
467
468
469
470
471
472
473
474
475

    //console.log("setNumberIDS - node: " + node + ", index: " + index +
    //            ", prefix: " + prefix);
    if (prefix == undefined) {
        prefix = "";
    }


    if (node.id != undefined) {

        if (node.id.substr(0, prefix.length) == prefix) {
            // The prefix is already there, do not readd it.
            prefix = "";
        }
        
        indexStart = node.id.length-3;
        endMatch = node.id.substr(indexStart, 3);

        if (endMatch == "_#n") {
            begin = node.id.slice(0, indexStart);
            node.id = prefix + begin + "_#" + index;
        } else {







>
|
>




|







463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484

    //console.log("setNumberIDS - node: " + node + ", index: " + index +
    //            ", prefix: " + prefix);
    if (prefix == undefined) {
        prefix = "";
    }

    // apply the change only if node.id  has a value
    // (node.id !== undefined && node.id !== "" && node.id !== null)
    if (node.id!==undefined && node.id!==null && node.id !== "") {
        if (node.id.substr(0, prefix.length) == prefix) {
            // The prefix is already there, do not readd it.
            prefix = "";
        }

        indexStart = node.id.length-3;
        endMatch = node.id.substr(indexStart, 3);

        if (endMatch == "_#n") {
            begin = node.id.slice(0, indexStart);
            node.id = prefix + begin + "_#" + index;
        } else {
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668

    //this.listeners = objectCopy(this.listeners);
    this.copyProperties = objectCopy(this.copyProperties);
    for (i = 0; i < this.copyProperties.length; i++) {
        property = this.copyProperties[i];
        this[property] = objectCopy(this[property]);
    }
    
    this.cloneProperties = objectCopy(this.cloneProperties);
    for (i=0; i < this.cloneProperties.length; i++) {
        property = this.cloneProperties[i];
        this[property] = clone(this[property]);
    }
};








|







663
664
665
666
667
668
669
670
671
672
673
674
675
676
677

    //this.listeners = objectCopy(this.listeners);
    this.copyProperties = objectCopy(this.copyProperties);
    for (i = 0; i < this.copyProperties.length; i++) {
        property = this.copyProperties[i];
        this[property] = objectCopy(this[property]);
    }

    this.cloneProperties = objectCopy(this.cloneProperties);
    for (i=0; i < this.cloneProperties.length; i++) {
        property = this.cloneProperties[i];
        this[property] = clone(this[property]);
    }
};

712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
 *
 * @param {string} property String name of the property that should be created,
 *       and marked to be auto-cloned (the object it points to is cloned).
 *
 * @param {Mixed} sourceObject The object from which clones will be made for
 *       'property' upon every clone of this Stork object.
 */
Stork.clonedProperty = function(property, sourceObject) {    
    this[property] = sourceObject;
    this.cloneProperties.push(property);
};


/**
 * Basic event management to cover some browser differences.







|







721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
 *
 * @param {string} property String name of the property that should be created,
 *       and marked to be auto-cloned (the object it points to is cloned).
 *
 * @param {Mixed} sourceObject The object from which clones will be made for
 *       'property' upon every clone of this Stork object.
 */
Stork.clonedProperty = function(property, sourceObject) {
    this[property] = sourceObject;
    this.cloneProperties.push(property);
};


/**
 * Basic event management to cover some browser differences.
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
    } else {
        return false;
    }
};


/**
 * @private
 * calls all the listeners for the model properties value change. The listeners
 * should have a method named "propertyChangeEvent" which receives parameters
 *  (model, property, oldvalue, newValue)
 *
 * @param {string} property name of the property changed
 * @param {Mixed} oldValue old value of the property changed
 * @param {Mixed} newValue new value of the property changed







|







933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
    } else {
        return false;
    }
};


/**
 * @protected
 * calls all the listeners for the model properties value change. The listeners
 * should have a method named "propertyChangeEvent" which receives parameters
 *  (model, property, oldvalue, newValue)
 *
 * @param {string} property name of the property changed
 * @param {Mixed} oldValue old value of the property changed
 * @param {Mixed} newValue new value of the property changed
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
    if (this.myElement != undefined) {
        this.refresh();
    }
};


/**
 * 
 *
 * @param model
 */
StorkController.unsetModel = function() {
    if (this.model) {
        this.model.removeListener(this);
    }







|







1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
    if (this.myElement != undefined) {
        this.refresh();
    }
};


/**
 *
 *
 * @param model
 */
StorkController.unsetModel = function() {
    if (this.model) {
        this.model.removeListener(this);
    }
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
StorkHtmlView.copiedProperty("textPropertyMethods", {});


/**
 * Initialises the view. If this object has a HTML element attached
 * to it (e.g. if it was cloned from an object that had that), then
 * that element will itself be cloned into a new element, but with all
 * 'id' attributes within the structure of that element changed to a new, 
 * unique ones.
 *
 * The mapping from the original names to the new ones will, however,
 * be retained, so that the elements can be retreived with
 * StorkHtmlView.getMyElementByID()
 */

StorkHtmlView.cloned = function() {
    this.superMethod(StorkHtmlView.cloned, "cloned");
    if (this.element) {
        this.elementIndex = getUniqueID();
        this.element = cloneNodeSetIDs(this.element, "StorkHtmlView-",
                                       this.elementIndex);
    }
    return this;
}


/**
 * Return the element based on the original 'id'. If this view has been
 * cloned from one with a HTML element linked to it, the IDs will be 
 * changed as part of cloning the original HTML element. This method 
 * allows access to the equivalent elements in this clone.
 */

StorkHtmlView.getMyElementByID = function(id) {
    if (this.element == undefined) {
        return null;
    }
    
    if (this.elementIndex != -1) {
        id = "StorkHtmlView-" + id + "_#" + this.elementIndex;
    }

    if (this.element.parentElement) {        
        return document.getElementById(id);
    } else {
        // The HTML element of this view is not in the document (yet), ie.
        // it's an orphan. Find the requested ID by going through the
        // HTML element.
        return containedElementByID(this.element, id);
    }







|




















|
|







|




|







1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
StorkHtmlView.copiedProperty("textPropertyMethods", {});


/**
 * Initialises the view. If this object has a HTML element attached
 * to it (e.g. if it was cloned from an object that had that), then
 * that element will itself be cloned into a new element, but with all
 * 'id' attributes within the structure of that element changed to a new,
 * unique ones.
 *
 * The mapping from the original names to the new ones will, however,
 * be retained, so that the elements can be retreived with
 * StorkHtmlView.getMyElementByID()
 */

StorkHtmlView.cloned = function() {
    this.superMethod(StorkHtmlView.cloned, "cloned");
    if (this.element) {
        this.elementIndex = getUniqueID();
        this.element = cloneNodeSetIDs(this.element, "StorkHtmlView-",
                                       this.elementIndex);
    }
    return this;
}


/**
 * Return the element based on the original 'id'. If this view has been
 * cloned from one with a HTML element linked to it, the IDs will be
 * changed as part of cloning the original HTML element. This method
 * allows access to the equivalent elements in this clone.
 */

StorkHtmlView.getMyElementByID = function(id) {
    if (this.element == undefined) {
        return null;
    }

    if (this.elementIndex != -1) {
        id = "StorkHtmlView-" + id + "_#" + this.elementIndex;
    }

    if (this.element.parentElement) {
        return document.getElementById(id);
    } else {
        // The HTML element of this view is not in the document (yet), ie.
        // it's an orphan. Find the requested ID by going through the
        // HTML element.
        return containedElementByID(this.element, id);
    }
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
        parentElement.appendChild(this.element);
    }
}


/**
 * Links a text property from the model to the content of an element. Note
 * that this completely empties the given element when the text property is 
 * changed, and replaces the content with the model's property value.
 *
 * @param modelProperty
 *        String that gives the name of the property from the model to link to
 *
 * @param id
 *        ID of the element which should have its text content altered.







|







1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
        parentElement.appendChild(this.element);
    }
}


/**
 * Links a text property from the model to the content of an element. Note
 * that this completely empties the given element when the text property is
 * changed, and replaces the content with the model's property value.
 *
 * @param modelProperty
 *        String that gives the name of the property from the model to link to
 *
 * @param id
 *        ID of the element which should have its text content altered.
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
/**
 * Links the result of a method call on the model to the textual content
 * of an element. Note that in this case there cannot be any automatic
 * updating from the model, but that the model itself has to inform
 * the controller (through refresh() or similar) of the change - in addition
 * to the update that happens as part of setModel().
 *
 * @param modelMethod  
 *     Method to call on the StorkModel to get the text content.
 * 
 * @param id
 *     ID of the element which should have its text content altered. 
 */

StorkHtmlView.linkPropertyMethodToText = function(modelMethod, id) {
    this.textPropertyMethods[modelMethod] = id;
};









|

|

|







1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
/**
 * Links the result of a method call on the model to the textual content
 * of an element. Note that in this case there cannot be any automatic
 * updating from the model, but that the model itself has to inform
 * the controller (through refresh() or similar) of the change - in addition
 * to the update that happens as part of setModel().
 *
 * @param modelMethod
 *     Method to call on the StorkModel to get the text content.
 *
 * @param id
 *     ID of the element which should have its text content altered.
 */

StorkHtmlView.linkPropertyMethodToText = function(modelMethod, id) {
    this.textPropertyMethods[modelMethod] = id;
};


1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
};


/**
 * Map a set of possible model property values to equivalent values for
 * an attribute of an element.
 *
 * @param modelProperty 
 *     The model property for which values should be checked.
 * @param id 
 *     ID of the element we should set the attribute for.
 * @param attrName 
 *     The attribute to be set in the HTML element.
 * @param propertyMap 
 *     An object with each property key matching a possible value
 *     for 'modelProperty' and with the value being the value to set
 *     'attrName'.
 */

StorkHtmlView.mapPropertyToAttr = function(modelProperty, id,
                                           attrName, propertyMap) {







|

|

|

|







1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
};


/**
 * Map a set of possible model property values to equivalent values for
 * an attribute of an element.
 *
 * @param modelProperty
 *     The model property for which values should be checked.
 * @param id
 *     ID of the element we should set the attribute for.
 * @param attrName
 *     The attribute to be set in the HTML element.
 * @param propertyMap
 *     An object with each property key matching a possible value
 *     for 'modelProperty' and with the value being the value to set
 *     'attrName'.
 */

StorkHtmlView.mapPropertyToAttr = function(modelProperty, id,
                                           attrName, propertyMap) {
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
 *
 * @param methodName
 *               The name of the method from this object to call when the
 *               model's property gets altered.
 *
 * @param elementIDMap
 *               If set, an object with each property mapped to an element
 *               ID. The property basically acts as a name for that 
 *               desired element. So that if the method in 'methodName'
 *               wants to change the text of a field giving a car's colour,
 *               as well as the model of the car,
 *               it can do that by looking up elementIds["colourElement"].
 *               This elementIDMap will be passed to 'methodName'.
 *               Deprecated:
 *               If set, is a list of IDs for elements that should be passed
 *               to the method called. 
 *               The actual elements will be looked up and passed to the method
 *               in its 'elements' argument.
 *
 *               If this is not defiend, 'elements' will not be passed to the
 *               method.
 *
 * The method will be called with either one or two arguments:







|







|







1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
 *
 * @param methodName
 *               The name of the method from this object to call when the
 *               model's property gets altered.
 *
 * @param elementIDMap
 *               If set, an object with each property mapped to an element
 *               ID. The property basically acts as a name for that
 *               desired element. So that if the method in 'methodName'
 *               wants to change the text of a field giving a car's colour,
 *               as well as the model of the car,
 *               it can do that by looking up elementIds["colourElement"].
 *               This elementIDMap will be passed to 'methodName'.
 *               Deprecated:
 *               If set, is a list of IDs for elements that should be passed
 *               to the method called.
 *               The actual elements will be looked up and passed to the method
 *               in its 'elements' argument.
 *
 *               If this is not defiend, 'elements' will not be passed to the
 *               method.
 *
 * The method will be called with either one or two arguments:
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
        return;
    }

    this._refreshTextProperties();
    this._refreshAttributesProperties();
    this._refreshMapAttributes();
    this._refreshPropertyMethods();
    this._refreshTextPropertyMethods(); 
};


/**
 * @private
 */
StorkHtmlView._refreshTextProperties = function() {







|







1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
        return;
    }

    this._refreshTextProperties();
    this._refreshAttributesProperties();
    this._refreshMapAttributes();
    this._refreshPropertyMethods();
    this._refreshTextPropertyMethods();
};


/**
 * @private
 */
StorkHtmlView._refreshTextProperties = function() {
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
 * @private
 */
StorkHtmlView._refreshTextPropertyMethods = function() {
    for(var key in this.textPropertyMethods) {
        if (this.textPropertyMethods[key] != undefined) {
            //var elementID = this.generateElementID(this.textPropertyMethods[key]);
            var elementID = this.textPropertyMethods[key];
            var textContainer = document.getElementById(elementID);
            if (textContainer != null) {
                emptyElement(textContainer);
                var text = this.model[key].call(this.model, []);
                textContainer.appendChild(document.createTextNode(text));
            }
        }
    }
};


/**
 * @private
 */
StorkHtmlView._refreshAttributesProperties = function() {
    for (var key in this.attrProperties) {
        if (this.attrProperties[key] != undefined) {
            var data = this.attrProperties[key];
            var elementID = data["id"];
            var element = document.getElementById(elementID);
            if (element != null) {
                var value = this.model.getProperty(key);
                element.setAttribute(data["attribute"], value);
            }
        }
    }
};


/**
 * @private
 */
StorkHtmlView._refreshMapAttributes = function() {
    for (var key in this.attrMaps) {
        var data = this.attrMaps[key];
        if (this.model.hasProperty(key)) {
            //var elementID = this.generateElementID(data["idPrefix"]);
            var elementID = data["id"];
            //console.log("createElement: elementID: " + elementID);
            var element = document.getElementById(elementID);

            if (element != undefined) {
                var map = data["propertyMap"];
                var value = this.model.getProperty(key);
                var mappedValue = map[value];
                if (mappedValue != undefined) {
                    element.setAttribute(data["attrName"], mappedValue);







|


















|



















|







1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
 * @private
 */
StorkHtmlView._refreshTextPropertyMethods = function() {
    for(var key in this.textPropertyMethods) {
        if (this.textPropertyMethods[key] != undefined) {
            //var elementID = this.generateElementID(this.textPropertyMethods[key]);
            var elementID = this.textPropertyMethods[key];
            var textContainer = this.getMyElementByID(elementID);
            if (textContainer != null) {
                emptyElement(textContainer);
                var text = this.model[key].call(this.model, []);
                textContainer.appendChild(document.createTextNode(text));
            }
        }
    }
};


/**
 * @private
 */
StorkHtmlView._refreshAttributesProperties = function() {
    for (var key in this.attrProperties) {
        if (this.attrProperties[key] != undefined) {
            var data = this.attrProperties[key];
            var elementID = data["id"];
            var element = this.getMyElementByID(elementID);
            if (element != null) {
                var value = this.model.getProperty(key);
                element.setAttribute(data["attribute"], value);
            }
        }
    }
};


/**
 * @private
 */
StorkHtmlView._refreshMapAttributes = function() {
    for (var key in this.attrMaps) {
        var data = this.attrMaps[key];
        if (this.model.hasProperty(key)) {
            //var elementID = this.generateElementID(data["idPrefix"]);
            var elementID = data["id"];
            //console.log("createElement: elementID: " + elementID);
            var element = this.getMyElementByID(elementID);

            if (element != undefined) {
                var map = data["propertyMap"];
                var value = this.model.getProperty(key);
                var mappedValue = map[value];
                if (mappedValue != undefined) {
                    element.setAttribute(data["attrName"], mappedValue);
Deleted test/cloneElement.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
  <head>
    <script type="text/javascript" src="../storkCore.js">
    </script>

    <style type="text/css">
      .test {
      border: solid red 3px;
      display: inline-block;
      }
    </style>

  </head>

<body>
<div id="container">
  <div class="test" id="testElement">
    Foobar
  </div>
</div>
</body>

    <script type="text/javascript">
      var element = elementByID("testElement");
      var container = elementByID("container");

      console.debug(element);
      container.removeChild(element);
      var element2 = clone(element);
      console.debug("id: " + element2.id + element2.getAttribute("id"));
      //element2.id = "test2";
      console.debug(element2);
      container.appendChild(element2);
    </script>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































Deleted test/cloned.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- Tests features for updating a view automatically based on properties
     in a model. Ie. binding views to model properties. -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>

    </head>
    <body>
      Should get two 'cloned' alerts.

      <script type="text/javascript">
        // Set details of an example person. Basic link testing.
        console.debug("Test starting");
        var personView = clone(StorkHtmlView);
        personView.cloned = function() {
            alert("cloned!");
        }
        var secondPersonView = clone(personView);
        var thirdPersonView = clone(secondPersonView);
      </script>
    </body>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































Deleted test/clonedProperty.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!-- Show how to build a list of lists with storkCore -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
   </head>
    <body>
      <p>
        Should say Hello, Setok, then Hello, Senna.
      </p>
      
          <script type="text/javascript">
            var testOb = clone(Stork);

            var cloneThis = clone(Stork);
            cloneThis.text = "hello, ";
            cloneThis.who = "Setok";
            console.debug("text: " + cloneThis.text + ", who: " + cloneThis.who);
            
            testOb.clonedProperty("subob", cloneThis);
            console.debug("testOb: ", testOb);
            
            var testOb2 = clone(testOb);
            testOb2.subob.who = "Senna";

            var testOb3 = clone(testOb2);
            testOb3.subob.who = "Ada";

            var subob = clone(testOb.subob);
            subob.who = "Juoni";

            alert("" + testOb.subob.text + testOb.subob.who);
            alert("" + testOb2.subob.text + testOb2.subob.who);

          </script>

    </body>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Deleted test/images/christening.jpg.

cannot compute difference between binary files

Deleted test/images/jris3.jpg.

cannot compute difference between binary files

Deleted test/inheritance.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../list.js">
            </script>
        
    </head>
    <body>
      <p>
        Should alert with:
      </p>
      <p>
        Hello Setok, Lawson
      </p>
      <p>
        Your cats: Juonipentu Retalainen Ada-san Hopo
      </p>
      <p>
        The end
      </p>
        
    </body>
    
    <script type="text/javascript">
        var ob1 = clone(Stork);
        
        ob1.hello = function(firstname, surname) {
            alert("hello, " + firstname + " " + surname);
            this.cats(["Juonipentu", "Retalainen", "Ada-san"]);            
        }
        
        ob1.cats = function(cats) {
            var catString = "";
            
            for (var i = 0; i < cats.length; i++) {
                catString = catString + cats[i] + " ";
            }
            
            alert("Your cats: " + catString);
        }
        
        var ob2 = clone(ob1);
        ob2.hello = function(firstname, surname) {
            this.superMethod(ob2.hello, "hello", firstname, surname);
            //ob1.hello.call(this, firstname, surname);
            alert("the end");
        }
        
        ob2.cats = function(cats) {
            cats.push("Hopo");
            this.superMethod(ob2.cats, "cats", cats); 
            //ob1.cats.call(this, cats);

        }
        
        var instance = clone(ob2);
        instance.hello("Setok", "Lawson");
    
        </script>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































Deleted test/keybinding.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
  <head>
    <script type="text/javascript" src="../storkCore.js">
    </script>
    <script type="text/javascript" src="../bean.js">
    </script>

  </head>

<body>
  <p>
    Enter capital 'F' or return to cause magic!
  </p>

  <form action="/">
    <input type="text" value="" name="" id="testField" />
  </form>
</body>

    <script type="text/javascript">
      var element = elementByID("testField");

      var ctrl = clone(StorkController);
      ctrl.foo = function(controller, key) {
      console.debug("foo keycode: " + key)
      alert("Hello, setok");
      }
      ctrl.attach(element);
      ctrl.bindKey("F", ctrl, "foo");
      ctrl.bindKey("<<return>>", ctrl, "foo");
    </script>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































Deleted test/linkModelToHtmlView.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<!-- Tests features for updating a view automatically based on properties
     in a model. Ie. binding views to model properties. -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>

    </head>
    <body>
      <p>
        The following person should be Setok:
      </p>
      <table id="person">
        <tr>
          <th>Name</th>
          <td id="name"></td>
        </tr>
        <tr>
          <th>Phone Number</th>
          <td id="phone"></td>
          </tr>
      </table>

      <p>
        The following should contain an exact replica of the first, within
        the red borders, even 
        reflecting
        the same model changes:
      </p>
      <div id="twinperson" style="border: solid 1px red;">
      </div>

      <p>
        The following person should have a completely different model,
        with the person being Senna
        (in green borders):
      </p>
      <div id="newperson" style="border: solid 1px green;">
      </div>

      <p id="attrTest">
        <span id="changeattr" class="foo">Check class changed to "bar"</span>
      </p>

      <div id="imageviewer">
      <p>
        The following should show a Caterham 7:
      </p>
      <img id="showimg" src="" />
      </div>

      <p id="colourTest">
        The colour of the day is: <span id="dayColour"></span>.
        (Should be British Racing Green)
      </p>

      <p id="random">
        Here's a random number: <span id="rndNumber"></span>.
      </p>

      <script type="text/javascript">
        // Set details of an example person. Basic link testing.
        var personView = clone(StorkHtmlView);
        //personView.init();
        personView.linkPropertyToText("name", "name");
        personView.linkPropertyToText("phone", "phone");
        personView.attach(elementByID("person"));

        StorkModel.setProperties({name: "Kris", phone: "04224242"});
        personView.setModel(StorkModel);

        var personView2 = clone(personView);
        //personView2.init();
        personView2.appendToElement(elementByID("twinperson"));

        var differentPerson = clone(personView2);
        //differentPerson.init();
        differentPerson.appendToElement(elementByID("newperson"));
        var differentModel = clone(StorkModel);
        differentModel.setProperty("name", "Senna");
        differentModel.setProperty("phone", "31337");
        differentPerson.setModel(differentModel);

        StorkModel.setProperty("name", "King");

        // Switch name of first (and thus second) person, every second
        var king = 1;
        setInterval(function() {
            if (king) {
                StorkModel.setProperty("name", "Setok");
            } else {
                StorkModel.setProperty("name", "King");
            }
            king = king ^ 1;
        }, 1000);

        // Test attribute linking
        var classChangeView = clone(StorkHtmlView);
        var model2 = clone(StorkModel);
        classChangeView.linkPropertyToAttr("class", "changeattr", "class");
        classChangeView.attach(elementByID("attrTest"));
        model2.setProperties({class: "bar"});
        classChangeView.setModel(model2);

        // Test mapping StorkModel properties to attribute values.
        // Note: should fetch these images localy so test is properly
        // encapsulated
        var imgView = clone(StorkHtmlView);
        var imgModel = clone(StorkModel);
        imgView.mapPropertyToAttr("image", "showimg", "src", {
            woman: "images/jris3.jpg",
            car: "images/christening.jpg"
        });
        imgView.attach(elementByID("imageviewer"));
        imgModel.setProperties({image: "car"});
        imgView.setModel(imgModel);

        // Test linking a property to a method
        var colourView = clone(StorkHtmlView);
        var colourModel = clone(StorkModel);
        colourView.colourUpdate = function(property, elementIDMap) {
           var colour;

           colour = this.model.getProperty(property);
           var element = elementIDMap["colourElement"];
           element = elementByID(element);
           emptyElement(element);
           if (colour == "green") {
              setTextOfElement(element, "British Racing Green");
           } else {
              setTextOfElement(element, "Ferrari Red");
           }
        }
        colourView.linkPropertyToMethod("colour", "colourUpdate",
                                        {colourElement: "dayColour"});
        colourModel.setProperties({colour: "green"});
        colourView.attach(elementByID("colourTest"));
        colourView.setModel(colourModel);

        // Test linking to a property provided by a method in the model
        var rndView = clone(StorkHtmlView);
        rndView.attach(elementByID("random"));
        rndView.linkPropertyMethodToText("number", "rndNumber");
        var rndModel = clone(StorkModel);        
        rndModel.number = function() {
            return Math.random();
        }
        rndView.setModel(rndModel);
      </script>
    </body>
</html>

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































Deleted test/list.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>
        <script type="text/javascript" src="../list.js">
            </script>
        <script type="text/javascript" src="../buttonController.js">
            </script>
        
    </head>
    <body>
      <p>
        List should be "fuu, Bar"
      </p>
      
        <ul id="list">  <!-- Container element -->
          <!-- Actual list items will be copied into here -->
        </ul>
        <div style="display: none;">
            <ul>
              <!-- 'Template' for each item in the list -->
              <li id="item_#n"><span id="text_#n"></span></li>
            </ul>
        </div>
    </body>
    
    <script type="text/javascript">
        ListController.setListContainer(elementByID("list"));
        
        ListItemController.setItemTemplate(elementByID("item_#n"));
        ListItemController.linkTextProperty("text", "text");
        ListController.setItemControllerPrototype(ListItemController);
        
        var model = [{text: "Foo"}, {text: "Bar"}];        
        ListModel.setItemsFromArray(model);
        ListController.setModel(ListModel);
        
        var itemModel = ListModel.getItemModel(0);
        itemModel.setProperty("text", "fuu"); 
        </script>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































Deleted test/listoflists.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!-- Show how to build a list of lists with storkCore -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../buttonController.js">
            </script>

        <script type="text/javascript" src="../bean.js">
            </script>

        <script type="text/javascript" src="../list.js">
            </script>
        
    </head>
    <body>
      <div style="border: solid red 2px;" id="sectionedList">
      </div>
      <div style="display: none;">
        <!-- section -->
        <div style="border: solid black 1px;" id="section_#n">
          <h3 id="sectionTitle_#n"></h3>
          <ul id="sublist_#n"></ul>
        </div>

        <ul>
          <!-- item in list in section -->
          <li id="item_#n"><span id="text_#n"></span></li>
        </ul>
        </div>
    </body>
    
    <script type="text/javascript">
      console.debug("ListOfListsItem sublistcontroller: ", ListOfListsItem.getSubListController());

        var sectionListController = clone(ListController);
        sectionListController.setListContainer(elementByID("sectionedList"));

        /* Set up what happens within each sublist */
        var subListController = ListOfListsItem.getSubListController();
        var subListItemController = clone(ListItemController);
        subListItemController.setItemTemplate(elementByID("item_#n"));
        subListItemController.linkTextProperty("text", "text");
        subListController.setItemControllerPrototype(subListItemController);

        /* Set up what is done with each section */
        var listOfListsItem = clone(ListOfListsItem);
        listOfListsItem.setItemTemplate(elementByID("section_#n"));
        listOfListsItem.linkTextProperty("title", "sectionTitle");
        /* The ID prefix of the element containing the sublist (will be
           "sublist_#n" in this case */
        listOfListsItem.setSubListContainerID("sublist");
        /* The property from each element that will contain the model for 
        the sublist */
        listOfListsItem.setSubListModelProperty("list");

        sectionListController.setItemControllerPrototype(listOfListsItem);
        
        // Section 1 items model
        var firstItems = [{text: "Foo"}, {text: "Bar"}];        
        var firstItemsModel = clone(ListModel);
        firstItemsModel.setItemsFromArray(firstItems);

        // Section 2 items model
        var secondItems = [{text: "Goo"}, {text: "Gah"}];        
        var secondItemsModel = clone(ListModel);
        secondItemsModel.setItemsFromArray(secondItems);

        // Make the two lists into items of the sction lists
        var sectionData = [{title: "Section 1", list: firstItemsModel},
        {title: "Section 2", list: secondItemsModel}];

        var sectionModel = clone(ListModel);
        sectionModel.setItemsFromArray(sectionData);

        sectionListController.setModel(sectionModel);
        </script>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































Deleted test/propertyTest.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Test Per-Object Properties</title>
</head>

<body>
<script type="text/javascript" src="../storkCore.js"></script>

<script>
  var modelOne = clone(StorkModel).init();
  console.log("model 1 before setup:");
  console.log(modelOne);
  modelOne.setProperty("title", "foo");

  var modelTwo = clone(StorkModel).init();
  modelTwo.setProperty("title", "bar");

  console.log("model 1:");
  console.log(modelOne);

  console.log("model 2:");
  console.log(modelTwo);
</script>
</body>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































Deleted test/propertyTest.html~.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Test Per-Object Properties</title>
</head>

<body>
<script type="text/javascript" src="../storkCore.js"></script>

<script>
  var modelOne = clone(StorkModel);
  modelOne.setProperty("title", "foo");

  var modelTwo = clone(StorkModel);
  modelTwo.setProperty("title", "bar");

  console.log("model 1:");
  console.log(modelOne);

  console.log("model 2:");
  console.log(modelTwo);
</script>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































Added tests/cloneElement.html.






































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
  <head>
    <script type="text/javascript" src="../storkCore.js">
    </script>

    <style type="text/css">
      .test {
      border: solid red 3px;
      display: inline-block;
      }
    </style>

  </head>

<body>
<div id="container">
  <div class="test" id="testElement">
    Foobar
  </div>
</div>
</body>

    <script type="text/javascript">
      var element = elementByID("testElement");
      var container = elementByID("container");

      console.debug(element);
      container.removeChild(element);
      var element2 = clone(element);
      console.debug("id: " + element2.id + element2.getAttribute("id"));
      //element2.id = "test2";
      console.debug(element2);
      container.appendChild(element2);
    </script>
</html>
Added tests/cloned.html.




















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- Tests features for updating a view automatically based on properties
     in a model. Ie. binding views to model properties. -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>

    </head>
    <body>
      Should get two 'cloned' alerts.

      <script type="text/javascript">
        // Set details of an example person. Basic link testing.
        console.debug("Test starting");
        var personView = clone(StorkHtmlView);
        personView.cloned = function() {
            alert("cloned!");
        }
        var secondPersonView = clone(personView);
        var thirdPersonView = clone(secondPersonView);
      </script>
    </body>
</html>
Added tests/images/christening.jpg.

cannot compute difference between binary files

Added tests/images/jris3.jpg.

cannot compute difference between binary files

Added tests/images/ladies.jpg.

cannot compute difference between binary files

Added tests/images/placeholder1.gif.

cannot compute difference between binary files

Added tests/images/placeholder2.gif.

cannot compute difference between binary files

Added tests/inheritance.html.


























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../list.js">
            </script>
        
    </head>
    <body>
      <p>
        Should alert with:
      </p>
      <p>
        Hello Setok, Lawson
      </p>
      <p>
        Your cats: Juonipentu Retalainen Ada-san Hopo
      </p>
      <p>
        The end
      </p>
        
    </body>
    
    <script type="text/javascript">
        var ob1 = clone(Stork);
        
        ob1.hello = function(firstname, surname) {
            alert("hello, " + firstname + " " + surname);
            this.cats(["Juonipentu", "Retalainen", "Ada-san"]);            
        }
        
        ob1.cats = function(cats) {
            var catString = "";
            
            for (var i = 0; i < cats.length; i++) {
                catString = catString + cats[i] + " ";
            }
            
            alert("Your cats: " + catString);
        }
        
        var ob2 = clone(ob1);
        ob2.hello = function(firstname, surname) {
            this.superMethod(ob2.hello, "hello", firstname, surname);
            //ob1.hello.call(this, firstname, surname);
            alert("the end");
        }
        
        ob2.cats = function(cats) {
            cats.push("Hopo");
            this.superMethod(ob2.cats, "cats", cats); 
            //ob1.cats.call(this, cats);

        }
        
        var instance = clone(ob2);
        instance.hello("Setok", "Lawson");
    
        </script>
</html>
Added tests/keybinding.html.
































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
  <head>
    <script type="text/javascript" src="../storkCore.js">
    </script>
    <script type="text/javascript" src="../bean.js">
    </script>

  </head>

<body>
  <p>
    Enter capital 'F' or return to cause magic!
  </p>

  <form action="/">
    <input type="text" value="" name="" id="testField" />
  </form>
</body>

    <script type="text/javascript">
      var element = elementByID("testField");

      var ctrl = clone(StorkController);
      ctrl.foo = function(controller, key) {
      console.debug("foo keycode: " + key)
      alert("Hello, setok");
      }
      ctrl.attach(element);
      ctrl.bindKey("F", ctrl, "foo");
      ctrl.bindKey("<<return>>", ctrl, "foo");
    </script>
</html>
Added tests/linkModelToHtmlView.html.






















































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<!-- Tests features for updating a view automatically based on properties
     in a model. Ie. binding views to model properties. -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>

    </head>
    <body>
      <p>
        The following person should be Setok:
      </p>
      <table id="person">
        <tr>
          <th>Name</th>
          <td id="name"></td>
        </tr>
        <tr>
          <th>Phone Number</th>
          <td id="phone"></td>
          </tr>
      </table>

      <p>
        The following should contain an exact replica of the first, within
        the red borders, even 
        reflecting
        the same model changes:
      </p>
      <div id="twinperson" style="border: solid 1px red;">
      </div>

      <p>
        The following person should have a completely different model,
        with the person being Senna
        (in green borders):
      </p>
      <div id="newperson" style="border: solid 1px green;">
      </div>

      <p id="attrTest">
        <span id="changeattr" class="foo">Check class changed to "bar"</span>
      </p>

      <div id="imageviewer">
      <p>
        The following should show a Caterham 7:
      </p>
      <img id="showimg" src="" />
      </div>

      <p id="colourTest">
        The colour of the day is: <span id="dayColour"></span>.
        (Should be British Racing Green)
      </p>

      <p id="random">
        Here's a random number: <span id="rndNumber"></span>.
      </p>

      <script type="text/javascript">
        // Set details of an example person. Basic link testing.
        var personView = clone(StorkHtmlView);
        //personView.init();
        personView.linkPropertyToText("name", "name");
        personView.linkPropertyToText("phone", "phone");
        personView.attach(elementByID("person"));

        StorkModel.setProperties({name: "Kris", phone: "04224242"});
        personView.setModel(StorkModel);

        var personView2 = clone(personView);
        //personView2.init();
        personView2.appendToElement(elementByID("twinperson"));

        var differentPerson = clone(personView2);
        //differentPerson.init();
        differentPerson.appendToElement(elementByID("newperson"));
        var differentModel = clone(StorkModel);
        differentModel.setProperty("name", "Senna");
        differentModel.setProperty("phone", "31337");
        differentPerson.setModel(differentModel);

        StorkModel.setProperty("name", "King");

        // Switch name of first (and thus second) person, every second
        var king = 1;
        setInterval(function() {
            if (king) {
                StorkModel.setProperty("name", "Setok");
            } else {
                StorkModel.setProperty("name", "King");
            }
            king = king ^ 1;
        }, 1000);

        // Test attribute linking
        var classChangeView = clone(StorkHtmlView);
        var model2 = clone(StorkModel);
        classChangeView.linkPropertyToAttr("class", "changeattr", "class");
        classChangeView.attach(elementByID("attrTest"));
        model2.setProperties({class: "bar"});
        classChangeView.setModel(model2);

        // Test mapping StorkModel properties to attribute values.
        // Note: should fetch these images localy so test is properly
        // encapsulated
        var imgView = clone(StorkHtmlView);
        var imgModel = clone(StorkModel);
        imgView.mapPropertyToAttr("image", "showimg", "src", {
            woman: "images/jris3.jpg",
            car: "images/christening.jpg"
        });
        imgView.attach(elementByID("imageviewer"));
        imgModel.setProperties({image: "car"});
        imgView.setModel(imgModel);

        // Test linking a property to a method
        var colourView = clone(StorkHtmlView);
        var colourModel = clone(StorkModel);
        colourView.colourUpdate = function(property, elementIDMap) {
           var colour;

           colour = this.model.getProperty(property);
           var element = elementIDMap["colourElement"];
           element = elementByID(element);
           emptyElement(element);
           if (colour == "green") {
              setTextOfElement(element, "British Racing Green");
           } else {
              setTextOfElement(element, "Ferrari Red");
           }
        }
        colourView.linkPropertyToMethod("colour", "colourUpdate",
                                        {colourElement: "dayColour"});
        colourModel.setProperties({colour: "green"});
        colourView.attach(elementByID("colourTest"));
        colourView.setModel(colourModel);

        // Test linking to a property provided by a method in the model
        var rndView = clone(StorkHtmlView);
        rndView.attach(elementByID("random"));
        rndView.linkPropertyMethodToText("number", "rndNumber");
        var rndModel = clone(StorkModel);        
        rndModel.number = function() {
            return Math.random();
        }
        rndView.setModel(rndModel);
      </script>
    </body>
</html>

Added tests/list.html.






















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../bean.js">
            </script>
        <script type="text/javascript" src="../list.js">
            </script>
        <script type="text/javascript" src="../buttonController.js">
            </script>
        
    </head>
    <body>
      <p>
        List should be "fuu, Bar"
      </p>
      
        <ul id="list">  <!-- Container element -->
          <!-- Actual list items will be copied into here -->
        </ul>
        <div style="display: none;">
            <ul>
              <!-- 'Template' for each item in the list -->
              <li id="item_#n"><span id="text_#n"></span></li>
            </ul>
        </div>
    </body>
    
    <script type="text/javascript">
        ListController.setListContainer(elementByID("list"));
        
        ListItemController.setItemTemplate(elementByID("item_#n"));
        ListItemController.linkTextProperty("text", "text");
        ListController.setItemControllerPrototype(ListItemController);
        
        var model = [{text: "Foo"}, {text: "Bar"}];        
        ListModel.setItemsFromArray(model);
        ListController.setModel(ListModel);
        
        var itemModel = ListModel.getItemModel(0);
        itemModel.setProperty("text", "fuu"); 
        </script>
</html>
Added tests/listfiltered.html.






















































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
<!DOCTYPE html>
<html>
  <head>
    <title>Mocha</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../../3rdparty/testing/mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>
    <script src="../../3rdparty/testing/mocha.js"></script>
    <script>mocha.setup('bdd')</script>
    <script src="../../3rdparty/testing/should.js"></script>
    <script src="../storkCore.js"></script>
    <script src="../buttonController.js"></script>
    <script src="../bean.js"></script>
    <script src="../list.js"></script>

    <script>
        var evenFilter = function(item){
            return (item.getProperty('number') % 2) == 0;
        };

        var data = [
            {name:"Three", number: 3},
            {name:"Four", number: 4},
            {name:"Five", number: 5},
            {name:"Six", number: 6},
            {name:"Seven", number: 7},
            {name:"Eight", number: 8},
            {name:"Nine", number: 9},
            {name:"Ten", number: 10},
            {name:"Eleven", number: 11},
            {name:"Tweleve", number: 12},
        ];
        var listModel = null,
            filteredListModel = null,
            excludedListmodel = null;

        describe('FilteredListModel', function(){

           beforeEach(function(){
                  listModel = clone(ListModel);
                  filteredListModel = clone(FilteredListModel);
                  filteredListModel.filterFn = evenFilter;
           });

           describe('List model initialization', function(){
              it('filtered list should include only even numbers when setting an initialized listModel', function(){
                listModel.setItemsFromArray(data);
                filteredListModel.setListModel(listModel);

                var item = null, i=0, len=filteredListModel.getLength();
                len.should.equal(5);
                for (;i<len;i++){
                    item = filteredListModel.getItemModel(i);
                    item.should.equal(listModel.getItemModel(i*2 + 1));
                    item.getProperty('number').should.equal(2 +((i+1) * 2));
                }
              });
          });

          describe('Event synchronization', function(){


              describe('Reset event', function(){
                it('filtered list should include only even numbers when original list model is set', function(){
                  filteredListModel.setListModel(listModel);
                  listModel.setItemsFromArray(data);
                  var item = null, i=0, len=filteredListModel.getLength();
                  len.should.equal(5);
                  for (;i<len;i++){
                      item = filteredListModel.getItemModel(i);
                      item.should.equal(listModel.getItemModel(i*2 + 1));
                      item.getProperty('number').should.equal(2 +((i+1) * 2));
                  }
                });
              });

              describe('Add event', function(){
                  beforeEach(function(){
                      listModel.setItemsFromArray(data);
                      filteredListModel.setListModel(listModel);
                  });

                  it('filtered list should NOT include added item not included by the filter', function(){
                      listModel.pushObject({
                          number:13,
                          name:"Thirteen"
                      });

                      var item = null, i=0, len=filteredListModel.getLength();
                      for (;i<len;i++){
                          item = filteredListModel.getItemModel(i);
                          item.getProperty('number').should.not.equal(13);
                      }
                  });
                  it('filtered list should include at the end item added at the end which is included by the filter', function(){
                      var fourteen = listModel.pushObject({
                          number:14,
                          name:"Fourteen"
                      });
                      filteredListModel.getItemModel(filteredListModel.getLength()-1).should.equal(fourteen);

                  });
                  it('filtered list should include at the beginning item added at the beginning which is included by the filter', function(){
                      var two = listModel.insertObjectBefore({
                          number:2,
                          name:"Two"
                      }, 0);
                      filteredListModel.getItemModel(0).should.equal(two);
                  });
                  it('filtered list should include in the right position an item added at a specific place which is included by the filter', function(){
                      // insert 2 between 6 and 8
                      var two = listModel.insertObjectBefore({
                          number:2,
                          name:"Two"
                      }, 4);
                      filteredListModel.getItemModel(2).should.equal(two);
                  });
              });

              describe('Delete event', function(){
                beforeEach(function(){
                      listModel.setItemsFromArray(data);
                      filteredListModel.setListModel(listModel);
                });

                it('filtered list should not do anything when item is removed from list and is not included by filter', function(){
                  var item = null, i=0, len=null;
                  // check 4 is in the list
                  item = filteredListModel.getItemModel(0);
                  item.getProperty('number').should.equal(4);

                  // delete 4
                  listModel.deleteIndex(1);

                  // check 4 is not in the list
                  for (i=0, len=filteredListModel.getLength() ; i<len ; i++){
                      item = filteredListModel.getItemModel(i);
                      item.getProperty('number').should.not.equal(4);
                  }
                });

                it('filtered list should remove item when item is removed from list and it is included by filter', function(){
                  var item = null, i=0, len=filteredListModel.getLength();
                  // check everything is in place
                  for (;i<len;i++){
                      item = filteredListModel.getItemModel(i);
                      item.should.equal(listModel.getItemModel(i*2 + 1));
                      item.getProperty('number').should.equal(2 +((i+1) * 2));
                  }
                  // delete 3
                  listModel.deleteIndex(0);
                  // check everything is in place
                  for (;i<len;i++){
                      item = filteredListModel.getItemModel(i);
                      item.should.equal(listModel.getItemModel(i*2 + 1));
                      item.getProperty('number').should.equal(2 +((i+1) * 2));
                  }
                });
              });

          }); // eo event synchronization

          describe('API blocked', function(){
              it('should not allow adding items',function(){
                (function(){
                    filteredListModel.pushObject({});
                }).should.throw();
                (function(){
                    filteredListModel.insertObjectBefore({},1);
                }).should.throw();
              });
              it('should not allow deleting items',function(){
                listModel.setItemsFromArray(data);
                filteredListModel.setListModel(listModel);

                (function(){
                    filteredListModel.deleteItem(filteredListModel.getItemModel(0));
                }).should.throw();
                (function(){
                    filteredListModel.deleteIndex(0);
                }).should.throw();
              });
              it('should not allow resetting data',function(){
                (function(){
                    filteredListModel.setItemsFromArray(data);
                }).should.throw();
              });
          });
    });

    describe('ExcludedListModel', function(){

           beforeEach(function(){
                  listModel = clone(ListModel);
                  excludedListModel = clone(ExcludedListModel);
                  excludedListModel.excludeFn = evenFilter;
           });

           describe('Exclusion works correctly', function(){
               it('exclusion list should not include even numbers when original list model is set', function(){
                  listModel.setItemsFromArray(data);
                  excludedListModel.setListModel(listModel);
                  var item = null, i=0, len=excludedListModel.getLength();
                  len.should.equal(5);
                  for (;i<len;i++){
                      item = excludedListModel.getItemModel(i);
                      item.should.equal(listModel.getItemModel(i*2));
                      item.getProperty('number').should.equal(1 +((i+1) * 2));
                  }
                });
           });
    });
    mocha.run();
    </script>
  </body>
</html>
Added tests/listoflists.html.






























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!-- Show how to build a list of lists with storkCore -->

<html>
    <head>
        <script type="text/javascript" src="../storkCore.js">
            </script>
        <script type="text/javascript" src="../buttonController.js">
            </script>

        <script type="text/javascript" src="../bean.js">
            </script>

        <script type="text/javascript" src="../list.js">
            </script>
        
    </head>
    <body>
      <div style="border: solid red 2px;" id="sectionedList">
      </div>
      <div style="display: none;">
        <!-- section -->
        <div style="border: solid black 1px;" id="section_#n">
          <h3 id="sectionTitle_#n"></h3>
          <ul id="sublist_#n"></ul>
        </div>

        <ul>
          <!-- item in list in section -->
          <li id="item_#n"><span id="text_#n"></span></li>
        </ul>
        </div>
    </body>
    
    <script type="text/javascript">
      console.debug("ListOfListsItem sublistcontroller: ", ListOfListsItem.getSubListController());

        var sectionListController = clone(ListController);
        sectionListController.setListContainer(elementByID("sectionedList"));

        /* Set up what happens within each sublist */
        var subListController = ListOfListsItem.getSubListController();
        var subListItemController = clone(ListItemController);
        subListItemController.setItemTemplate(elementByID("item_#n"));
        subListItemController.linkTextProperty("text", "text");
        subListController.setItemControllerPrototype(subListItemController);

        /* Set up what is done with each section */
        var listOfListsItem = clone(ListOfListsItem);
        listOfListsItem.setItemTemplate(elementByID("section_#n"));
        listOfListsItem.linkTextProperty("title", "sectionTitle");
        /* The ID prefix of the element containing the sublist (will be
           "sublist_#n" in this case */
        listOfListsItem.setSubListContainerID("sublist");
        /* The property from each element that will contain the model for 
        the sublist */
        listOfListsItem.setSubListModelProperty("list");

        sectionListController.setItemControllerPrototype(listOfListsItem);
        
        // Section 1 items model
        var firstItems = [{text: "Foo"}, {text: "Bar"}];        
        var firstItemsModel = clone(ListModel);
        firstItemsModel.setItemsFromArray(firstItems);

        // Section 2 items model
        var secondItems = [{text: "Goo"}, {text: "Gah"}];        
        var secondItemsModel = clone(ListModel);
        secondItemsModel.setItemsFromArray(secondItems);

        // Make the two lists into items of the sction lists
        var sectionData = [{title: "Section 1", list: firstItemsModel},
        {title: "Section 2", list: secondItemsModel}];

        var sectionModel = clone(ListModel);
        sectionModel.setItemsFromArray(sectionData);

        sectionListController.setModel(sectionModel);
        </script>
</html>