]> git.wh0rd.org Git - tt-rss.git/blob - lib/CheckBoxStoreModel.js
scroll handler: performance improvements
[tt-rss.git] / lib / CheckBoxStoreModel.js
1 //dojo.provide("lib.CheckBoxTree");
2 //dojo.provide("lib.CheckBoxStoreModel");
3
4 // THIS WIDGET IS BASED ON DOJO/DIJIT 1.4.0 AND WILL NOT WORK WITH PREVIOUS VERSIONS
5 //
6 //      Release date: 02/05/2010
7 //
8
9 //dojo.require("dijit.Tree");
10 //dojo.require("dijit.form.CheckBox");
11
12 define(["dojo/_base/declare", "dijit/tree/TreeStoreModel"], function (declare) {
13
14         return declare( "lib.CheckBoxStoreModel", dijit.tree.TreeStoreModel,
15                 {
16                         // checkboxAll: Boolean
17                         //              If true, every node in the tree will receive a checkbox regardless if the 'checkbox' attribute
18                         //              is specified in the dojo.data.
19                         checkboxAll: true,
20
21                         // checkboxState: Boolean
22                         //              The default state applied to every checkbox unless otherwise specified in the dojo.data.
23                         //              (see also: checkboxIdent)
24                         checkboxState: false,
25
26                         // checkboxRoot: Boolean
27                         //              If true, the root node will receive a checkbox eventhough it's not a true entry in the store.
28                         //              This attribute is independent of the showRoot attribute of the tree itself. If the tree
29                         //              attribute 'showRoot' is set to false to checkbox for the root will not show either.
30                         checkboxRoot: false,
31
32                         // checkboxStrict: Boolean
33                         //              If true, a strict parent-child checkbox relation is maintained. For example, if all children
34                         //              are checked the parent will automatically be checked or if any of the children are unchecked
35                         //              the parent will be unchecked.
36                         checkboxStrict: true,
37
38                         // checkboxIdent: String
39                         //              The attribute name (attribute of the dojo.data.item) that specifies that items checkbox initial
40                         //              state. Example: { name:'Egypt', type:'country', checkbox: true }
41                         //              If a dojo.data.item has no 'checkbox' attribute specified it will depend on the attribute
42                         //              'checkboxAll' if one will be created automatically and if so what the initial state will be as
43                         //              specified by 'checkboxState'.
44                         checkboxIdent: "checkbox",
45
46                         updateCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
47                                 // summary:
48                                 //              Update the checkbox state (true/false) for the item and the associated parent and
49                                 //              child checkboxes if any.
50                                 // description:
51                                 //              Update a single checkbox state (true/false) for the item and the associated parent
52                                 //              and child checkboxes if any. This function is called from the tree if a user checked
53                                 //              or unchecked a checkbox on the tree. The parent and child tree nodes are updated to
54                                 //              maintain consistency if 'checkboxStrict' is set to true.
55                                 //      storeItem:
56                                 //              The item in the dojo.data.store whos checkbox state needs updating.
57                                 //      newState:
58                                 //              The new state of the checkbox: true or false
59                                 //      example:
60                                 //      | model.updateCheckboxState(item, true);
61                                 //
62
63                                 this._setCheckboxState( storeItem, newState );
64                                 //if( this.checkboxStrict ) { I don't need all this 1-1 stuff, only parent -> child (fox)
65                                 this._updateChildCheckbox( storeItem, newState );
66                                 //this._updateParentCheckbox( storeItem, newState );
67                                 //}
68                         },
69                         setAllChecked: function(checked) {
70                                 var items = this.store._arrayOfAllItems;
71                                 this.setCheckboxState(items, checked);
72                         },
73                         setCheckboxState: function(items, checked) {
74                                 for (var i = 0; i < items.length; i++) {
75                                         this._setCheckboxState(items[i], checked);
76                                 }
77                         },
78                         getCheckedItems: function() {
79                                 var items = this.store._arrayOfAllItems;
80                                 var result = [];
81
82                                 for (var i = 0; i < items.length; i++) {
83                                         if (this.store.getValue(items[i], 'checkbox'))
84                                                 result.push(items[i]);
85                                 }
86
87                                 return result;
88                         },
89
90                         getCheckboxState: function(/*dojo.data.Item*/ storeItem) {
91                                 // summary:
92                                 //              Get the current checkbox state from the dojo.data.store.
93                                 // description:
94                                 //              Get the current checkbox state from the dojo.data store. A checkbox can have three
95                                 //              different states: true, false or undefined. Undefined in this context means no
96                                 //              checkbox identifier (checkboxIdent) was found in the dojo.data store. Depending on
97                                 //              the checkbox attributes as specified above the following will take place:
98                                 //              a)      If the current checkbox state is undefined and the checkbox attribute 'checkboxAll' or
99                                 //                      'checkboxRoot' is true one will be created and the default state 'checkboxState' will
100                                 //                      be applied.
101                                 //              b)      If the current state is undefined and 'checkboxAll' is false the state undefined remains
102                                 //                      unchanged and is returned. This will prevent any tree node from creating a checkbox.
103                                 //
104                                 //      storeItem:
105                                 //              The item in the dojo.data.store whos checkbox state is returned.
106                                 //      example:
107                                 //      | var currState = model.getCheckboxState(item);
108                                 //
109                                 var currState = undefined;
110
111                                 // Special handling required for the 'fake' root entry (the root is NOT a dojo.data.item).
112                                 // this stuff is only relevant for Forest store -fox
113                                 /*              if ( storeItem == this.root ) {
114                                  if( typeof(storeItem.checkbox) == "undefined" ) {
115                                  this.root.checkbox = undefined;                // create a new checbox reference as undefined.
116                                  if( this.checkboxRoot ) {
117                                  currState = this.root.checkbox = this.checkboxState;
118                                  }
119                                  } else {
120                                  currState = this.root.checkbox;
121                                  }
122                                  } else {       // a valid dojo.store.item
123                                  currState = this.store.getValue(storeItem, this.checkboxIdent);
124                                  if( currState == undefined && this.checkboxAll) {
125                                  this._setCheckboxState( storeItem, this.checkboxState );
126                                  currState = this.checkboxState;
127                                  }
128                                  } */
129
130                                 currState = this.store.getValue(storeItem, this.checkboxIdent);
131                                 if( currState == undefined && this.checkboxAll) {
132                                         this._setCheckboxState( storeItem, this.checkboxState );
133                                         currState = this.checkboxState;
134                                 }
135
136                                 return currState;  // the current state of the checkbox (true/false or undefined)
137                         },
138
139                         _setCheckboxState: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
140                                 // summary:
141                                 //              Set/update the checkbox state on the dojo.data store.
142                                 // description:
143                                 //              Set/update the checkbox state on the dojo.data.store. Retreive the current
144                                 //              state of the checkbox and validate if an update is required, this will keep
145                                 //              update events to a minimum. On completion a 'onCheckboxChange' event is
146                                 //              triggered.
147                                 //              If the current state is undefined (ie: no checkbox attribute specified for
148                                 //              this dojo.data.item) the 'checkboxAll' attribute is checked     to see if one
149                                 //              needs to be created. In case of the root the 'checkboxRoot' attribute is checked.
150                                 //              NOTE: the store.setValue function will create the 'checkbox' attribute for the
151                                 //              item if none exists.
152                                 //      storeItem:
153                                 //              The item in the dojo.data.store whos checkbox state is updated.
154                                 //      newState:
155                                 //              The new state of the checkbox: true or false
156                                 //      example:
157                                 //      | model.setCheckboxState(item, true);
158                                 //
159                                 var stateChanged = true;
160
161                                 if( storeItem != this.root ) {
162                                         var currState = this.store.getValue(storeItem, this.checkboxIdent);
163                                         if( currState != newState && ( currState !== undefined || this.checkboxAll ) ) {
164                                                 this.store.setValue(storeItem, this.checkboxIdent, newState);
165                                         } else {
166                                                 stateChanged = false;   // No changes to the checkbox
167                                         }
168                                 } else {  // Tree root instance
169                                         if( this.root.checkbox != newState && ( this.root.checkbox !== undefined || this.checkboxRoot ) ) {
170                                                 this.root.checkbox = newState;
171                                         } else {
172                                                 stateChanged = false;
173                                         }
174                                 }
175                                 if( stateChanged ) {    // In case of any changes trigger the update event.
176                                         this.onCheckboxChange(storeItem);
177                                 }
178                                 return stateChanged;
179                         },
180
181                         _updateChildCheckbox: function(/*dojo.data.Item*/ parentItem, /*Boolean*/ newState ) {
182                                 //      summary:
183                                 //              Set all child checkboxes to true/false depending on the parent checkbox state.
184                                 //      description:
185                                 //              If a parent checkbox changes state, all child and grandchild checkboxes will be
186                                 //              updated to reflect the change. For example, if the parent state is set to true,
187                                 //              all child and grandchild checkboxes will receive that same 'true' state.
188                                 //              If a child checkbox changes state and has multiple parent, all of its parents
189                                 //              need to be re-evaluated.
190                                 //      parentItem:
191                                 //              The parent dojo.data.item whos child/grandchild checkboxes require updating.
192                                 //      newState:
193                                 //              The new state of the checkbox: true or false
194                                 //
195
196                                 if( this.mayHaveChildren( parentItem )) {
197                                         this.getChildren( parentItem, dojo.hitch( this,
198                                                 function( children ) {
199                                                         dojo.forEach( children, function(child) {
200                                                                 if( this._setCheckboxState(child, newState) ) {
201                                                                         var parents = this._getParentsItem(child);
202                                                                         if( parents.length > 1 ) {
203                                                                                 this._updateParentCheckbox( child, newState );
204                                                                         }
205                                                                 }
206                                                                 if( this.mayHaveChildren( child )) {
207                                                                         this._updateChildCheckbox( child, newState );
208                                                                 }
209                                                         }, this );
210                                                 }),
211                                                 function(err) {
212                                                         console.error(this, ": updating child checkboxes: ", err);
213                                                 }
214                                         );
215                                 }
216                         },
217
218                         _updateParentCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
219                                 //      summary:
220                                 //              Update the parent checkbox state depending on the state of all child checkboxes.
221                                 //      description:
222                                 //              Update the parent checkbox state depending on the state of all child checkboxes.
223                                 //              The parent checkbox automatically changes state if ALL child checkboxes are true
224                                 //              or false. If, as a result, the parent checkbox changes state, we will check if
225                                 //              its parent needs to be updated as well all the way upto the root.
226                                 //      storeItem:
227                                 //              The dojo.data.item whos parent checkboxes require updating.
228                                 //      newState:
229                                 //              The new state of the checkbox: true or false
230                                 //
231                                 var parents = this._getParentsItem(storeItem);
232                                 dojo.forEach( parents, function( parentItem ) {
233                                         if( newState ) { // new state = true (checked)
234                                                 this.getChildren( parentItem, dojo.hitch( this,
235                                                         function(siblings) {
236                                                                 var allChecked  = true;
237                                                                 dojo.some( siblings, function(sibling) {
238                                                                         siblState = this.getCheckboxState(sibling);
239                                                                         if( siblState !== undefined && allChecked )
240                                                                                 allChecked = siblState;
241                                                                         return !(allChecked);
242                                                                 }, this );
243                                                                 if( allChecked ) {
244                                                                         this._setCheckboxState( parentItem, true );
245                                                                         this._updateParentCheckbox( parentItem, true );
246                                                                 }
247                                                         }),
248                                                         function(err) {
249                                                                 console.error(this, ": updating parent checkboxes: ", err);
250                                                         }
251                                                 );
252                                         } else {        // new state = false (unchecked)
253                                                 if( this._setCheckboxState( parentItem, false ) ) {
254                                                         this._updateParentCheckbox( parentItem, false );
255                                                 }
256                                         }
257                                 }, this );
258                         },
259
260                         _getParentsItem: function(/*dojo.data.Item*/ storeItem ) {
261                                 // summary:
262                                 //              Get the parent(s) of a dojo.data item.
263                                 // description:
264                                 //              Get the parent(s) of a dojo.data item. The '_reverseRefMap' entry of the item is
265                                 //              used to identify the parent(s). A child will have a parent reference if the parent
266                                 //              specified the '_reference' attribute.
267                                 //              For example: children:[{_reference:'Mexico'}, {_reference:'Canada'}, ...
268                                 //      storeItem:
269                                 //              The dojo.data.item whos parent(s) will be returned.
270                                 //
271                                 var parents = [];
272
273                                 if( storeItem != this.root ) {
274                                         var references = storeItem[this.store._reverseRefMap];
275                                         for(itemId in references ) {
276                                                 parents.push(this.store._itemsByIdentity[itemId]);
277                                         }
278                                         if (!parents.length) {
279                                                 parents.push(this.root);
280                                         }
281                                 }
282                                 return parents; // parent(s) of a dojo.data.item (Array of dojo.data.items)
283                         },
284
285                         validateData: function(/*dojo.data.Item*/ storeItem, /*thisObject*/ scope ) {
286                                 // summary:
287                                 //              Validate/normalize the parent(s) checkbox data in the dojo.data store.
288                                 // description:
289                                 //              Validate/normalize the parent-child checkbox relationship if the attribute
290                                 //              'checkboxStrict' is set to true. This function is called as part of the post
291                                 //              creation of the Tree instance. All parent checkboxes are set to the appropriate
292                                 //              state according to the actual state(s) of their children.
293                                 //              This will potentionally overwrite whatever was specified for the parent in the
294                                 //              dojo.data store. This will garantee the tree is in a consistent state after startup.
295                                 //      storeItem:
296                                 //              The element to start traversing the dojo.data.store, typically model.root
297                                 //      scope:
298                                 //              The scope to use when this method executes.
299                                 //      example:
300                                 //      | this.model.validateData(this.model.root, this.model);
301                                 //
302                                 if( !scope.checkboxStrict ) {
303                                         return;
304                                 }
305                                 scope.getChildren( storeItem, dojo.hitch( scope,
306                                         function(children) {
307                                                 var allChecked  = true;
308                                                 var childState;
309                                                 dojo.forEach( children, function( child ) {
310                                                         if( this.mayHaveChildren( child )) {
311                                                                 this.validateData( child, this );
312                                                         }
313                                                         childState = this.getCheckboxState( child );
314                                                         if( childState !== undefined && allChecked )
315                                                                 allChecked = childState;
316                                                 }, this);
317
318                                                 if ( this._setCheckboxState( storeItem, allChecked) ) {
319                                                         this._updateParentCheckbox( storeItem, allChecked);
320                                                 }
321                                         }),
322                                         function(err) {
323                                                 console.error(this, ": validating checkbox data: ", err);
324                                         }
325                                 );
326                         },
327
328                         onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
329                                 // summary:
330                                 //              Callback whenever a checkbox state has changed state, so that
331                                 //              the Tree can update the checkbox.  This callback is generally
332                                 //              triggered by the '_setCheckboxState' function.
333                                 // tags:
334                                 //              callback
335                         }
336
337                 });
338
339 });
340
341