source: Dev/trunk/src/client/dojox/widget/RollingList.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 36.1 KB
Line 
1dojo.provide("dojox.widget.RollingList");
2dojo.experimental("dojox.widget.RollingList");
3
4dojo.require("dojo.window");
5
6dojo.require("dijit.layout.ContentPane");
7dojo.require("dijit._Templated");
8dojo.require("dijit._Contained");
9dojo.require("dijit.layout._LayoutWidget");
10dojo.require("dijit.Menu");
11dojo.require("dijit.form.Button");
12dojo.require("dijit.focus");            // dijit.focus()
13dojo.require("dijit._base.focus");      // dijit.getFocus()
14
15dojo.require("dojox.html.metrics");
16
17dojo.require("dojo.i18n");
18dojo.requireLocalization("dijit", "common");
19
20dojo.declare("dojox.widget._RollingListPane",
21        [dijit.layout.ContentPane, dijit._Templated, dijit._Contained], {
22        // summary:
23        //              a core pane that can be attached to a RollingList.  All panes
24        //              should extend this one
25
26        // templateString: string
27        //              our template
28        templateString: '<div class="dojoxRollingListPane"><table><tbody><tr><td dojoAttachPoint="containerNode"></td></tr></tbody></div>',
29
30        // parentWidget: dojox.widget.RollingList
31        //              Our rolling list widget
32        parentWidget: null,
33       
34        // parentPane: dojox.widget._RollingListPane
35        //              The pane that immediately precedes ours
36        parentPane: null,
37                       
38        // store: store
39        //              the store we must use
40        store: null,
41
42        // items: item[]
43        //              an array of (possibly not-yet-loaded) items to display in this.
44        //              If this array is null, then the query and query options are used to
45        //              get the top-level items to use.  This array is also used to watch and
46        //              see if the pane needs to be reloaded (store notifications are handled)
47        //              by the pane
48        items: null,
49       
50        // query: object
51        //              a query to pass to the datastore.  This is only used if items are null
52        query: null,
53       
54        // queryOptions: object
55        //              query options to be passed to the datastore
56        queryOptions: null,
57       
58        // focusByNode: boolean
59        //              set to false if the subclass will handle its own node focusing
60        _focusByNode: true,
61       
62        // minWidth: integer
63        //              the width (in px) for this pane
64        minWidth: 0,
65       
66        _setContentAndScroll: function(/*String|DomNode|Nodelist*/cont, /*Boolean?*/isFakeContent){
67                // summary:
68                //              sets the value of the content and scrolls it into view
69                this._setContent(cont, isFakeContent);
70                this.parentWidget.scrollIntoView(this);
71        },
72
73        _updateNodeWidth: function(n, min){
74                // summary:
75                //              updates the min width of the pane to be minPaneWidth
76                n.style.width = "";
77                var nWidth = dojo.marginBox(n).w;
78                if(nWidth < min){
79                        dojo.marginBox(n, {w: min});
80                }
81        },
82       
83        _onMinWidthChange: function(v){
84                // Called when the min width of a pane has changed
85                this._updateNodeWidth(this.domNode, v);
86        },
87       
88        _setMinWidthAttr: function(v){
89                if(v !== this.minWidth){
90                        this.minWidth = v;
91                        this._onMinWidthChange(v);
92                }
93        },
94       
95        startup: function(){
96                if(this._started){ return; }
97                if(this.store && this.store.getFeatures()["dojo.data.api.Notification"]){
98                        window.setTimeout(dojo.hitch(this, function(){
99                                // Set connections after a slight timeout to avoid getting in the
100                                // condition where we are setting them while events are still
101                                // being fired
102                                this.connect(this.store, "onSet", "_onSetItem");
103                                this.connect(this.store, "onNew", "_onNewItem");
104                                this.connect(this.store, "onDelete", "_onDeleteItem");
105                        }), 1);
106                }
107                this.connect(this.focusNode||this.domNode, "onkeypress", "_focusKey");
108                this.parentWidget._updateClass(this.domNode, "Pane");
109                this.inherited(arguments);
110                this._onMinWidthChange(this.minWidth);
111        },
112
113        _focusKey: function(/*Event*/e){
114                // summary:
115                //              called when a keypress happens on the widget
116                if(e.charOrCode == dojo.keys.BACKSPACE){
117                        dojo.stopEvent(e);
118                        return;
119                }else if(e.charOrCode == dojo.keys.LEFT_ARROW && this.parentPane){
120                        this.parentPane.focus();
121                        this.parentWidget.scrollIntoView(this.parentPane);
122                }else if(e.charOrCode == dojo.keys.ENTER){
123                        this.parentWidget._onExecute();
124                }
125        },
126       
127        focus: function(/*boolean*/force){
128                // summary:
129                //              sets the focus to this current widget
130                if(this.parentWidget._focusedPane != this){
131                        this.parentWidget._focusedPane = this;
132                        this.parentWidget.scrollIntoView(this);
133                        if(this._focusByNode && (!this.parentWidget._savedFocus || force)){
134                                try{(this.focusNode||this.domNode).focus();}catch(e){}
135                        }
136                }
137        },
138
139        _onShow: function(){
140                // summary:
141                //              checks that the store is loaded
142                this.inherited(arguments);
143                if((this.store || this.items) && ((this.refreshOnShow && this.domNode) || (!this.isLoaded && this.domNode))){
144                        this.refresh();
145                }
146        },
147
148        _load: function(){
149                // summary:
150                //              sets the "loading" message and then kicks off a query asyncronously
151                this.isLoaded = false;
152                if(this.items){
153                        this._setContentAndScroll(this.onLoadStart(), true);
154                        window.setTimeout(dojo.hitch(this, "_doQuery"), 1);
155                }else{
156                        this._doQuery();
157                }
158        },
159       
160        _doLoadItems: function(/*item[]*/items, /*function*/callback){
161                // summary:
162                //              loads the given items, and then calls the callback when they
163                //              are finished.
164                var _waitCount = 0, store = this.store;
165                dojo.forEach(items, function(item){
166                        if(!store.isItemLoaded(item)){ _waitCount++; }
167                });
168                if(_waitCount === 0){
169                        callback();
170                }else{
171                        var onItem = function(item){
172                                _waitCount--;
173                                if((_waitCount) === 0){
174                                        callback();
175                                }
176                        };
177                        dojo.forEach(items, function(item){
178                                if(!store.isItemLoaded(item)){
179                                        store.loadItem({item: item, onItem: onItem});
180                                }
181                        });
182                }
183        },
184       
185        _doQuery: function(){
186                // summary:
187                //              either runs the query or loads potentially not-yet-loaded items.
188                if(!this.domNode){return;}
189                var preload = this.parentWidget.preloadItems;
190                preload = (preload === true || (this.items && this.items.length <= Number(preload)));
191                if(this.items && preload){
192                        this._doLoadItems(this.items, dojo.hitch(this, "onItems"));
193                }else if(this.items){
194                        this.onItems();
195                }else{
196                        this._setContentAndScroll(this.onFetchStart(), true);
197                        this.store.fetch({query: this.query,
198                                onComplete: function(items){
199                                        this.items = items;
200                                        this.onItems();
201                                },
202                                onError: function(e){
203                                        this._onError("Fetch", e);
204                                },
205                                scope: this});
206                }
207        },
208
209        _hasItem: function(/* item */ item){
210                // summary:
211                //              returns whether or not the given item is handled by this
212                //              pane
213                var items = this.items || [];
214                for(var i = 0, myItem; (myItem = items[i]); i++){
215                        if(this.parentWidget._itemsMatch(myItem, item)){
216                                return true;
217                        }
218                }
219                return false;
220        },
221       
222        _onSetItem: function(/* item */ item,
223                                        /* attribute-name-string */ attribute,
224                                        /* Object|Array */ oldValue,
225                                        /* Object|Array */ newValue){
226                // summary:
227                //              called when an item in the store has changed
228                if(this._hasItem(item)){
229                        this.refresh();
230                }
231        },
232       
233        _onNewItem: function(/* item */ newItem, /*object?*/ parentInfo){
234                // summary:
235                //              called when an item is added to the store
236                var sel;
237                if((!parentInfo && !this.parentPane) ||
238                        (parentInfo && this.parentPane && this.parentPane._hasItem(parentInfo.item) &&
239                        (sel = this.parentPane._getSelected()) && this.parentWidget._itemsMatch(sel.item, parentInfo.item))){
240                        this.items.push(newItem);
241                        this.refresh();
242                }else if(parentInfo && this.parentPane && this._hasItem(parentInfo.item)){
243                        this.refresh();
244                }
245        },
246       
247        _onDeleteItem: function(/* item */ deletedItem){
248                // summary:
249                //              called when an item is removed from the store
250                if(this._hasItem(deletedItem)){
251                        this.items = dojo.filter(this.items, function(i){
252                                return (i != deletedItem);
253                        });
254                        this.refresh();
255                }
256        },
257       
258        onFetchStart: function(){
259                // summary:
260                //              called before a fetch starts
261                return this.loadingMessage;
262        },
263       
264        onFetchError: function(/*Error*/ error){
265                // summary:
266                //              called when a fetch error occurs.
267                return this.errorMessage;
268        },
269
270        onLoadStart: function(){
271                // summary:
272                //              called before a load starts
273                return this.loadingMessage;
274        },
275       
276        onLoadError: function(/*Error*/ error){
277                // summary:
278                //              called when a load error occurs.
279                return this.errorMessage;
280        },
281       
282        onItems: function(){
283                // summary:
284                //              called after a fetch or load - at this point, this.items should be
285                //      set and loaded.  Override this function to "do your stuff"
286                if(!this.onLoadDeferred){
287                        this.cancel();
288                        this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
289                }
290                this._onLoadHandler();
291        }
292                       
293});
294
295dojo.declare("dojox.widget._RollingListGroupPane",
296        [dojox.widget._RollingListPane], {
297        // summary:
298        //              a pane that will handle groups (treats them as menu items)
299       
300        // templateString: string
301        //              our template
302        templateString: '<div><div dojoAttachPoint="containerNode"></div>' +
303                                        '<div dojoAttachPoint="menuContainer">' +
304                                                '<div dojoAttachPoint="menuNode"></div>' +
305                                        '</div></div>',
306
307        // _menu: dijit.Menu
308        //              The menu that we will call addChild() on for adding items
309        _menu: null,
310       
311        _setContent: function(/*String|DomNode|Nodelist*/cont){
312                if(!this._menu){
313                        // Only set the content if we don't already have a menu
314                        this.inherited(arguments);
315                }
316        },
317
318        _onMinWidthChange: function(v){
319                // override and resize the menu instead
320                if(!this._menu){ return; }
321                var dWidth = dojo.marginBox(this.domNode).w;
322                var mWidth = dojo.marginBox(this._menu.domNode).w;
323                this._updateNodeWidth(this._menu.domNode, v - (dWidth - mWidth));
324        },
325
326        onItems: function(){
327                // summary:
328                //              called after a fetch or load
329                var selectItem, hadChildren = false;
330                if(this._menu){
331                        selectItem = this._getSelected();
332                        this._menu.destroyRecursive();
333                }
334                this._menu = this._getMenu();
335                var child, selectMenuItem;
336                if(this.items.length){
337                        dojo.forEach(this.items, function(item){
338                                child = this.parentWidget._getMenuItemForItem(item, this);
339                                if(child){
340                                        if(selectItem && this.parentWidget._itemsMatch(child.item, selectItem.item)){
341                                                selectMenuItem = child;
342                                        }
343                                        this._menu.addChild(child);
344                                }
345                        }, this);
346                }else{
347                        child = this.parentWidget._getMenuItemForItem(null, this);
348                        if(child){
349                                this._menu.addChild(child);
350                        }
351                }
352                if(selectMenuItem){
353                        this._setSelected(selectMenuItem);
354                        if((selectItem && !selectItem.children && selectMenuItem.children) ||
355                                (selectItem && selectItem.children && !selectMenuItem.children)){
356                                var itemPane = this.parentWidget._getPaneForItem(selectMenuItem.item, this, selectMenuItem.children);
357                                if(itemPane){
358                                        this.parentWidget.addChild(itemPane, this.getIndexInParent() + 1);
359                                }else{
360                                        this.parentWidget._removeAfter(this);
361                                        this.parentWidget._onItemClick(null, this, selectMenuItem.item, selectMenuItem.children);
362                                }
363                        }
364                }else if(selectItem){
365                        this.parentWidget._removeAfter(this);
366                }
367                this.containerNode.innerHTML = "";
368                this.containerNode.appendChild(this._menu.domNode);
369                this.parentWidget.scrollIntoView(this);
370                this._checkScrollConnection(true);
371                this.inherited(arguments);
372                this._onMinWidthChange(this.minWidth);
373        },
374       
375        _checkScrollConnection: function(doLoad){
376                // summary:
377                //              checks whether or not we need to connect to our onscroll
378                //              function
379                var store = this.store;
380                if(this._scrollConn){
381                        this.disconnect(this._scrollConn);
382                }
383                delete this._scrollConn;
384                if(!dojo.every(this.items, function(i){return store.isItemLoaded(i);})){
385                        if(doLoad){
386                                this._loadVisibleItems();
387                        }
388                        this._scrollConn = this.connect(this.domNode, "onscroll", "_onScrollPane");
389                }
390        },
391       
392        startup: function(){
393                this.inherited(arguments);
394                this.parentWidget._updateClass(this.domNode, "GroupPane");
395        },
396       
397        focus: function(/*boolean*/force){
398                // summary:
399                //              sets the focus to this current widget
400                if(this._menu){
401                        if(this._pendingFocus){
402                                this.disconnect(this._pendingFocus);
403                        }
404                        delete this._pendingFocus;
405                       
406                        // We focus the right widget - either the focusedChild, the
407                        // selected node, the first menu item, or the menu itself
408                        var focusWidget = this._menu.focusedChild;
409                        if(!focusWidget){
410                                var focusNode = dojo.query(".dojoxRollingListItemSelected", this.domNode)[0];
411                                if(focusNode){
412                                        focusWidget = dijit.byNode(focusNode);
413                                }
414                        }
415                        if(!focusWidget){
416                                focusWidget = this._menu.getChildren()[0] || this._menu;
417                        }
418                        this._focusByNode = false;
419                        if(focusWidget.focusNode){
420                                if(!this.parentWidget._savedFocus || force){
421                                        try{focusWidget.focusNode.focus();}catch(e){}
422                                }
423                                window.setTimeout(function(){
424                                        try{
425                                                dojo.window.scrollIntoView(focusWidget.focusNode);
426                                        }catch(e){}
427                                }, 1);
428                        }else if(focusWidget.focus){
429                                if(!this.parentWidget._savedFocus || force){
430                                        focusWidget.focus();
431                                }
432                        }else{
433                                this._focusByNode = true;
434                        }
435                        this.inherited(arguments);
436                }else if(!this._pendingFocus){
437                        this._pendingFocus = this.connect(this, "onItems", "focus");
438                }
439        },
440       
441        _getMenu: function(){
442                // summary:
443                //              returns a widget to be used for the container widget.
444                var self = this;
445                var menu = new dijit.Menu({
446                        parentMenu: this.parentPane ? this.parentPane._menu : null,
447                        onCancel: function(/*Boolean*/ closeAll){
448                                if(self.parentPane){
449                                        self.parentPane.focus(true);
450                                }
451                        },
452                        _moveToPopup: function(/*Event*/ evt){
453                                if(this.focusedChild && !this.focusedChild.disabled){
454                                        this.onItemClick(this.focusedChild, evt);
455                                }
456                        }
457                }, this.menuNode);
458                this.connect(menu, "onItemClick", function(/*dijit.MenuItem*/ item, /*Event*/ evt){
459                        if(item.disabled){ return; }
460                        evt.alreadySelected = dojo.hasClass(item.domNode, "dojoxRollingListItemSelected");
461                        if(evt.alreadySelected &&
462                                ((evt.type == "keypress" && evt.charOrCode != dojo.keys.ENTER) ||
463                                (evt.type == "internal"))){
464                                var p = this.parentWidget.getChildren()[this.getIndexInParent() + 1];
465                                if(p){
466                                        p.focus(true);
467                                        this.parentWidget.scrollIntoView(p);
468                                }
469                        }else{
470                                this._setSelected(item, menu);
471                                this.parentWidget._onItemClick(evt, this, item.item, item.children);
472                                if(evt.type == "keypress" && evt.charOrCode == dojo.keys.ENTER){
473                                        this.parentWidget._onExecute();
474                                }
475                        }
476                });
477                if(!menu._started){
478                        menu.startup();
479                }
480                return menu;
481        },
482       
483        _onScrollPane: function(){
484                // summary:
485                //              called when the pane has been scrolled - it sets a timeout
486                //              so that we don't try and load our visible items too often during
487                //              a scroll
488                if(this._visibleLoadPending){
489                        window.clearTimeout(this._visibleLoadPending);
490                }
491                this._visibleLoadPending = window.setTimeout(dojo.hitch(this, "_loadVisibleItems"), 500);
492        },
493       
494        _loadVisibleItems: function(){
495                // summary:
496                //              loads the items that are currently visible in the pane
497                delete this._visibleLoadPending;
498                var menu = this._menu;
499                if(!menu){ return; }
500                var children = menu.getChildren();
501                if(!children || !children.length){ return; }
502                var gpbme = function(n, m, pb){
503                        var s = dojo.getComputedStyle(n);
504                        var r = 0;
505                        if(m){ r += dojo._getMarginExtents(n, s).t; }
506                        if(pb){ r += dojo._getPadBorderExtents(n, s).t; }
507                        return r;
508                };
509                var topOffset = gpbme(this.domNode, false, true) +
510                                                gpbme(this.containerNode, true, true) +
511                                                gpbme(menu.domNode, true, true) +
512                                                gpbme(children[0].domNode, true, false);
513                var h = dojo.contentBox(this.domNode).h;
514                var minOffset = this.domNode.scrollTop - topOffset - (h/2);
515                var maxOffset = minOffset + (3*h/2);
516                var menuItemsToLoad = dojo.filter(children, function(c){
517                        var cnt = c.domNode.offsetTop;
518                        var s = c.store;
519                        var i = c.item;
520                        return (cnt >= minOffset && cnt <= maxOffset && !s.isItemLoaded(i));
521                })
522                var itemsToLoad = dojo.map(menuItemsToLoad, function(c){
523                        return c.item;
524                });
525                var onItems = dojo.hitch(this, function(){
526                        var selectItem = this._getSelected();
527                        var selectMenuItem;
528                        dojo.forEach(itemsToLoad, function(item, idx){
529                                var newItem = this.parentWidget._getMenuItemForItem(item, this);
530                                var oItem = menuItemsToLoad[idx];
531                                var oIdx = oItem.getIndexInParent();
532                                menu.removeChild(oItem);
533                                if(newItem){
534                                        if(selectItem && this.parentWidget._itemsMatch(newItem.item, selectItem.item)){
535                                                selectMenuItem = newItem;
536                                        }
537                                        menu.addChild(newItem, oIdx);
538                                        if(menu.focusedChild == oItem){
539                                                menu.focusChild(newItem);
540                                        }
541                                }
542                                oItem.destroy();
543                        }, this);
544                        this._checkScrollConnection(false);
545                });
546                this._doLoadItems(itemsToLoad, onItems);
547        },
548       
549        _getSelected: function(/*dijit.Menu?*/ menu){
550                // summary:
551                //              returns the selected menu item - or null if none are selected
552                if(!menu){ menu = this._menu; }
553                if(menu){
554                        var children = this._menu.getChildren();
555                        for(var i = 0, item; (item = children[i]); i++){
556                                if(dojo.hasClass(item.domNode, "dojoxRollingListItemSelected")){
557                                        return item;
558                                }
559                        }
560                }
561                return null;
562        },
563       
564        _setSelected: function(/*dijit.MenuItem?*/ item, /*dijit.Menu?*/ menu){
565                // summary:
566                //              selects the given item in the given menu (defaults to pane's menu)
567                if(!menu){ menu = this._menu;}
568                if(menu){
569                        dojo.forEach(menu.getChildren(), function(i){
570                                this.parentWidget._updateClass(i.domNode, "Item", {"Selected": (item && (i == item && !i.disabled))});
571                        }, this);
572                }
573        }
574});
575
576dojo.declare("dojox.widget.RollingList",
577        [dijit._Widget, dijit._Templated, dijit._Container], {
578        // summary:
579        //              a rolling list that can be tied to a data store with children
580               
581        // templateString: String
582        //              The template to be used to construct the widget.
583        templateString: dojo.cache("dojox.widget", "RollingList/RollingList.html"),
584        widgetsInTemplate: true,
585       
586        // className: string
587        //              an additional class (or space-separated classes) to add for our widget
588        className: "",
589       
590        // store: store
591        //              the store we must use
592        store: null,
593       
594        // query: object
595        //              a query to pass to the datastore.  This is only used if items are null
596        query: null,
597       
598        // queryOptions: object
599        //              query options to be passed to the datastore
600        queryOptions: null,
601       
602        // childrenAttrs: String[]
603        //              one ore more attributes that holds children of a node
604        childrenAttrs: ["children"],
605
606        // parentAttr: string
607        //              the attribute to read for finding our parent item (if any)
608        parentAttr: "",
609       
610        // value: item
611        //              The value that has been selected
612        value: null,
613       
614        // executeOnDblClick: boolean
615        //              Set to true if you want to call onExecute when an item is
616        //              double-clicked, false if you want to call onExecute yourself. (mainly
617        //              used for popups to control how they want to be handled)
618        executeOnDblClick: true,
619       
620        // preloadItems: boolean or int
621        //              if set to true, then onItems will be called only *after* all items have
622        //              been loaded (ie store.isLoaded will return true for all of them).  If
623        //              false, then no preloading will occur.  If set to an integer, preloading
624        //              will occur if the number of items is less than or equal to the value
625        //              of the integer.  The onItems function will need to be aware of handling
626        //              items that may not be loaded
627        preloadItems: false,
628       
629        // showButtons: boolean
630        //              if set to true, then buttons for "OK" and "Cancel" will be provided
631        showButtons: false,
632       
633        // okButtonLabel: string
634        //              The string to use for the OK button - will use dijit's common "OK" string
635        //              if not set
636        okButtonLabel: "",
637       
638        // cancelButtonLabel: string
639        //              The string to use for the Cancel button - will use dijit's common
640        //              "Cancel" string if not set
641        cancelButtonLabel: "",
642
643        // minPaneWidth: integer
644        //              the minimum pane width (in px) for all child panes.  If they are narrower,
645        //              the width will be increased to this value.
646        minPaneWidth: 0,
647       
648        postMixInProperties: function(){
649                // summary:
650                //              Mix in our labels, if they are not set
651                this.inherited(arguments);
652                var loc = dojo.i18n.getLocalization("dijit", "common");
653                this.okButtonLabel = this.okButtonLabel || loc.buttonOk;
654                this.cancelButtonLabel = this.cancelButtonLabel || loc.buttonCancel;
655        },
656       
657        _setShowButtonsAttr: function(doShow){
658                // summary:
659                //              Sets the visibility of the buttons for the widget
660                var needsLayout = false;
661                if((this.showButtons != doShow && this._started) ||
662                        (this.showButtons == doShow && !this.started)){
663                        needsLayout = true;
664                }
665                dojo.toggleClass(this.domNode, "dojoxRollingListButtonsHidden", !doShow);
666                this.showButtons = doShow;
667                if(needsLayout){
668                        if(this._started){
669                                this.layout();
670                        }else{
671                                window.setTimeout(dojo.hitch(this, "layout"), 0);
672                        }
673                }
674        },
675       
676        _itemsMatch: function(/*item*/ item1, /*item*/ item2){
677                // summary:
678                //              returns whether or not the two items match - checks ID if
679                //              they aren't the exact same object
680                if(!item1 && !item2){
681                        return true;
682                }else if(!item1 || !item2){
683                        return false;
684                }
685                return (item1 == item2 ||
686                        (this._isIdentity && this.store.getIdentity(item1) == this.store.getIdentity(item2)));
687        },
688       
689        _removeAfter: function(/*Widget or int*/ idx){
690                // summary:
691                //              removes all widgets after the given widget (or index)
692                if(typeof idx != "number"){
693                        idx = this.getIndexOfChild(idx);
694                }
695                if(idx >= 0){
696                        dojo.forEach(this.getChildren(), function(c, i){
697                                if(i > idx){
698                                        this.removeChild(c);
699                                        c.destroyRecursive();
700                                }
701                        }, this);
702                }
703                var children = this.getChildren(), child = children[children.length - 1];
704                var selItem = null;
705                while(child && !selItem){
706                        var val = child._getSelected ? child._getSelected() : null;
707                        if(val){
708                                selItem = val.item;
709                        }
710                        child = child.parentPane;
711                }
712                if(!this._setInProgress){
713                        this._setValue(selItem);
714                }
715        },
716       
717        addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
718                // summary:
719                //              adds a child to this rolling list - if passed an insertIndex,
720                //              then all children from that index on will be removed and destroyed
721                //              before adding the child.
722                if(insertIndex > 0){
723                        this._removeAfter(insertIndex - 1);
724                }
725                this.inherited(arguments);
726                if(!widget._started){
727                        widget.startup();
728                }
729                widget.attr("minWidth", this.minPaneWidth);
730                this.layout();
731                if(!this._savedFocus){
732                        widget.focus();
733                }
734        },
735       
736        _setMinPaneWidthAttr: function(value){
737                // summary:
738                //              Sets the min pane width of all children
739                if(value !== this.minPaneWidth){
740                        this.minPaneWidth = value;
741                        dojo.forEach(this.getChildren(), function(c){
742                                c.attr("minWidth", value);
743                        });
744                }
745        },
746       
747        _updateClass: function(/* Node */ node, /* String */ type, /* Object? */ options){
748                // summary:
749                //              sets the state of the given node with the given type and options
750                // options:
751                //              an object with key-value-pairs.  The values are boolean, if true,
752                //              the key is added as a class, if false, it is removed.
753                if(!this._declaredClasses){
754                        this._declaredClasses = ("dojoxRollingList " + this.className).split(" ");
755                }
756                dojo.forEach(this._declaredClasses, function(c){
757                        if(c){
758                                dojo.addClass(node, c + type);
759                                for(var k in options||{}){
760                                        dojo.toggleClass(node, c + type + k, options[k]);
761                                }
762                                dojo.toggleClass(node, c + type + "FocusSelected",
763                                        (dojo.hasClass(node, c + type + "Focus") && dojo.hasClass(node, c + type + "Selected")));
764                                dojo.toggleClass(node, c + type + "HoverSelected",
765                                        (dojo.hasClass(node, c + type + "Hover") && dojo.hasClass(node, c + type + "Selected")));
766                        }
767                });
768        },
769       
770        scrollIntoView: function(/*dijit._Widget*/ childWidget){
771                // summary:
772                //              scrolls the given widget into view
773                if(this._scrollingTimeout){
774                        window.clearTimeout(this._scrollingTimeout);
775                }
776                delete this._scrollingTimeout;
777                this._scrollingTimeout = window.setTimeout(dojo.hitch(this, function(){
778                        if(childWidget.domNode){
779                                dojo.window.scrollIntoView(childWidget.domNode);
780                        }
781                        delete this._scrollingTimeout;
782                        return;
783                }), 1);
784        },
785       
786        resize: function(args){
787                dijit.layout._LayoutWidget.prototype.resize.call(this, args);
788        },
789       
790        layout: function(){
791                var children = this.getChildren();
792                if(this._contentBox){
793                        var bn = this.buttonsNode;
794                        var height = this._contentBox.h - dojo.marginBox(bn).h -
795                                                                dojox.html.metrics.getScrollbar().h;
796                        dojo.forEach(children, function(c){
797                                dojo.marginBox(c.domNode, {h: height});
798                        });
799                }
800                if(this._focusedPane){
801                        var foc = this._focusedPane;
802                        delete this._focusedPane;
803                        if(!this._savedFocus){
804                                foc.focus();
805                        }
806                }else if(children && children.length){
807                        if(!this._savedFocus){
808                                children[0].focus();
809                        }
810                }
811        },
812       
813        _onChange: function(/*item*/ value){
814                this.onChange(value);
815        },
816
817        _setValue: function(/* item */ value){
818                // summary:
819                //              internally sets the value and fires onchange
820                delete this._setInProgress;
821                if(!this._itemsMatch(this.value, value)){
822                        this.value = value;
823                        this._onChange(value);
824                }
825        },
826       
827        _setValueAttr: function(/* item */ value){
828                // summary:
829                //              sets the value of this widget to the given store item
830                if(this._itemsMatch(this.value, value) && !value){ return; }
831                if(this._setInProgress && this._setInProgress === value){ return; }
832                this._setInProgress = value;
833                if(!value || !this.store.isItem(value)){
834                        var pane = this.getChildren()[0];
835                        pane._setSelected(null);
836                        this._onItemClick(null, pane, null, null);
837                        return;
838                }
839               
840                var fetchParentItems = dojo.hitch(this, function(/*item*/ item, /*function*/callback){
841                        // summary:
842                        //              Fetches the parent items for the given item
843                        var store = this.store, id;
844                        if(this.parentAttr && store.getFeatures()["dojo.data.api.Identity"] &&
845                                ((id = this.store.getValue(item, this.parentAttr)) || id === "")){
846                                // Fetch by parent attribute
847                                var cb = function(i){
848                                        if(store.getIdentity(i) == store.getIdentity(item)){
849                                                callback(null);
850                                        }else{
851                                                callback([i]);
852                                        }
853                                };
854                                if(id === ""){
855                                        callback(null);
856                                }else if(typeof id == "string"){
857                                        store.fetchItemByIdentity({identity: id, onItem: cb});
858                                }else if(store.isItem(id)){
859                                        cb(id);
860                                }
861                        }else{
862                                // Fetch by finding children
863                                var numCheck = this.childrenAttrs.length;
864                                var parents = [];
865                                dojo.forEach(this.childrenAttrs, function(attr){
866                                        var q = {};
867                                        q[attr] = item;
868                                        store.fetch({query: q, scope: this,
869                                                onComplete: function(items){
870                                                        if(this._setInProgress !== value){
871                                                                return;
872                                                        }
873                                                        parents = parents.concat(items);
874                                                        numCheck--;
875                                                        if(numCheck === 0){
876                                                                callback(parents);
877                                                        }
878                                                }
879                                        });
880                                }, this);
881                        }
882                });
883               
884                var setFromChain = dojo.hitch(this, function(/*item[]*/itemChain, /*integer*/idx){
885                        // summary:
886                        //              Sets the value of the widget at the given index in the chain - onchanges are not
887                        //              fired here
888                        var set = itemChain[idx];
889                        var child = this.getChildren()[idx];
890                        var conn;
891                        if(set && child){
892                                var fx = dojo.hitch(this, function(){
893                                        if(conn){
894                                                this.disconnect(conn);
895                                        }
896                                        delete conn;
897                                        if(this._setInProgress !== value){
898                                                return;
899                                        }
900                                        var selOpt = dojo.filter(child._menu.getChildren(), function(i){
901                                                return this._itemsMatch(i.item, set);
902                                        }, this)[0];
903                                        if(selOpt){
904                                                idx++;
905                                                child._menu.onItemClick(selOpt, {type: "internal",
906                                                                                                        stopPropagation: function(){},
907                                                                                                        preventDefault: function(){}});
908                                                if(itemChain[idx]){
909                                                        setFromChain(itemChain, idx);
910                                                }else{
911                                                        this._setValue(set);
912                                                        this.onItemClick(set, child, this.getChildItems(set));
913                                                }
914                                        }
915                                });
916                                if(!child.isLoaded){
917                                        conn = this.connect(child, "onLoad", fx);
918                                }else{
919                                        fx();
920                                }
921                        }else if(idx === 0){
922                                this.set("value", null);
923                        }
924                });
925               
926                var parentChain = [];
927                var onParents = dojo.hitch(this, function(/*item[]*/ parents){
928                        // summary:
929                        //              recursively grabs the parents - only the first one is followed
930                        if(parents && parents.length){
931                                parentChain.push(parents[0]);
932                                fetchParentItems(parents[0], onParents);
933                        }else{
934                                if(!parents){
935                                        parentChain.pop();
936                                }
937                                parentChain.reverse();
938                                setFromChain(parentChain, 0);
939                        }
940                });
941               
942                // Only set the value in display if we are shown - if we are in a dropdown,
943                // and are hidden, don't actually do the scrolling in the display (it can
944                // mess up layouts)
945                var ns = this.domNode.style;
946                if(ns.display == "none" || ns.visibility == "hidden"){
947                        this._setValue(value);
948                }else if(!this._itemsMatch(value, this._visibleItem)){
949                        onParents([value]);
950                }
951        },
952       
953        _onItemClick: function(/* Event */ evt, /* dijit._Contained */ pane, /* item */ item, /* item[]? */ children){
954                // summary:
955                //              internally called when a widget should pop up its child
956               
957                if(evt){
958                        var itemPane = this._getPaneForItem(item, pane, children);
959                        var alreadySelected = (evt.type == "click" && evt.alreadySelected);
960
961                        if(alreadySelected && itemPane){
962                                this._removeAfter(pane.getIndexInParent() + 1);
963                                var next = pane.getNextSibling();
964                                if(next && next._setSelected){
965                                        next._setSelected(null);
966                                }
967                                this.scrollIntoView(next);
968                        }else if(itemPane){
969                                this.addChild(itemPane, pane.getIndexInParent() + 1);
970                                if(this._savedFocus){
971                                        itemPane.focus(true);
972                                }
973                        }else{
974                                this._removeAfter(pane);
975                                this.scrollIntoView(pane);
976                        }
977                }else if(pane){
978                        this._removeAfter(pane);
979                        this.scrollIntoView(pane);
980                }
981                if(!evt || evt.type != "internal"){
982                        this._setValue(item);
983                        this.onItemClick(item, pane, children);
984                }
985                this._visibleItem = item;
986        },
987       
988        _getPaneForItem: function(/* item? */ item, /* dijit._Contained? */ parentPane, /* item[]? */ children){
989                // summary:
990                //              gets the pane for the given item, and mixes in our needed parts
991                //              Returns the pane for the given item (null if the root pane) - after mixing in
992                //              its stuff.
993                var ret = this.getPaneForItem(item, parentPane, children);
994                ret.store = this.store;
995                ret.parentWidget = this;
996                ret.parentPane = parentPane||null;
997                if(!item){
998                        ret.query = this.query;
999                        ret.queryOptions = this.queryOptions;
1000                }else if(children){
1001                        ret.items = children;
1002                }else{
1003                        ret.items = [item];
1004                }
1005                return ret;
1006        },
1007       
1008        _getMenuItemForItem: function(/*item*/ item, /* dijit._Contained */ parentPane){
1009                // summary:
1010                //              returns a widget for the given store item.  The returned
1011                //              item will be added to this widget's container widget.  null will
1012                //              be passed in for an "empty" item.
1013                var store = this.store;
1014                if(!item || !store || !store.isItem(item)){
1015                        var i = new dijit.MenuItem({
1016                                label: "---",
1017                                disabled: true,
1018                                iconClass: "dojoxEmpty",
1019                                focus: function(){
1020                                        // Do nothing on focus of this guy...
1021                                }
1022                        });
1023                        this._updateClass(i.domNode, "Item");
1024                        return i;
1025                }else{
1026                        var itemLoaded = store.isItemLoaded(item);
1027                        var childItems = itemLoaded ? this.getChildItems(item) : undefined;
1028                        var widgetItem;
1029                        if(childItems){
1030                                widgetItem = this.getMenuItemForItem(item, parentPane, childItems);
1031                                widgetItem.children = childItems;
1032                                this._updateClass(widgetItem.domNode, "Item", {"Expanding": true});
1033                                if(!widgetItem._started){
1034                                        var c = widgetItem.connect(widgetItem, "startup", function(){
1035                                                this.disconnect(c);
1036                                                dojo.style(this.arrowWrapper, "visibility", "");
1037                                        });
1038                                }else{
1039                                        dojo.style(widgetItem.arrowWrapper, "visibility", "");
1040                                }
1041                        }else{
1042                                widgetItem = this.getMenuItemForItem(item, parentPane, null);
1043                                if(itemLoaded){
1044                                        this._updateClass(widgetItem.domNode, "Item", {"Single": true});
1045                                }else{
1046                                        this._updateClass(widgetItem.domNode, "Item", {"Unloaded": true});
1047                                        widgetItem.attr("disabled", true);
1048                                }
1049                        }
1050                        widgetItem.store = this.store;
1051                        widgetItem.item = item;
1052                        if(!widgetItem.label){
1053                                widgetItem.attr("label", this.store.getLabel(item).replace(/</,"&lt;"));
1054                        }
1055                        if(widgetItem.focusNode){
1056                                var self = this;
1057                                widgetItem.focus = function(){
1058                                        // Don't set our class
1059                                        if(!this.disabled){try{this.focusNode.focus();}catch(e){}}
1060                                };
1061                                widgetItem.connect(widgetItem.focusNode, "onmouseenter", function(){
1062                                        if(!this.disabled){
1063                                                self._updateClass(this.domNode, "Item", {"Hover": true});
1064                                        }
1065                                });
1066                                widgetItem.connect(widgetItem.focusNode, "onmouseleave", function(){
1067                                        if(!this.disabled){
1068                                                self._updateClass(this.domNode, "Item", {"Hover": false});
1069                                        }
1070                                });
1071                                widgetItem.connect(widgetItem.focusNode, "blur", function(){
1072                                        self._updateClass(this.domNode, "Item", {"Focus": false, "Hover": false});
1073                                });
1074                                widgetItem.connect(widgetItem.focusNode, "focus", function(){
1075                                        self._updateClass(this.domNode, "Item", {"Focus": true});
1076                                        self._focusedPane = parentPane;
1077                                });
1078                                if(this.executeOnDblClick){
1079                                        widgetItem.connect(widgetItem.focusNode, "ondblclick", function(){
1080                                                self._onExecute();
1081                                        });
1082                                }
1083                        }
1084                        return widgetItem;
1085                }
1086        },
1087       
1088        _setStore: function(/* dojo/data/api/Read */ store){
1089                // summary:
1090                //              sets the store for this widget */
1091                if(store === this.store && this._started){ return; }
1092                this.store = store;
1093                this._isIdentity = store.getFeatures()["dojo.data.api.Identity"];
1094                var rootPane = this._getPaneForItem();
1095                this.addChild(rootPane, 0);
1096        },
1097       
1098        _onKey: function(/*Event*/ e){
1099                // summary:
1100                //              called when a keypress event happens on this widget
1101                if(e.charOrCode == dojo.keys.BACKSPACE){
1102                        dojo.stopEvent(e);
1103                        return;
1104                }else if(e.charOrCode == dojo.keys.ESCAPE && this._savedFocus){
1105                        try{dijit.focus(this._savedFocus);}catch(e){}
1106                        dojo.stopEvent(e);
1107                        return;
1108                }else if(e.charOrCode == dojo.keys.LEFT_ARROW ||
1109                        e.charOrCode == dojo.keys.RIGHT_ARROW){
1110                        dojo.stopEvent(e);
1111                        return;
1112                }
1113        },
1114       
1115        _resetValue: function(){
1116                // summary:
1117                //              function called when the value is reset.
1118                this.set("value", this._lastExecutedValue);
1119        },
1120       
1121        _onCancel: function(){
1122                // summary:
1123                //              function called when the cancel button is clicked.  It
1124                //              resets its value to whatever was last executed and then cancels
1125                this._resetValue();
1126                this.onCancel();
1127        },
1128       
1129        _onExecute: function(){
1130                // summary:
1131                //              function called when the OK button is clicked or when an
1132                //              item is selected (double-clicked or "enter" pressed on it)
1133                this._lastExecutedValue = this.get("value");
1134                this.onExecute();
1135        },
1136       
1137        focus: function(){
1138                // summary:
1139                //              sets the focus state of this widget
1140                var wasSaved = this._savedFocus;
1141                this._savedFocus = dijit.getFocus(this);
1142                if(!this._savedFocus.node){
1143                        delete this._savedFocus;
1144                }
1145                if(!this._focusedPane){
1146                        var child = this.getChildren()[0];
1147                        if(child && !wasSaved){
1148                                child.focus(true);
1149                        }
1150                }else{
1151                        this._savedFocus = dijit.getFocus(this);
1152                        var foc = this._focusedPane;
1153                        delete this._focusedPane;
1154                        if(!wasSaved){
1155                                foc.focus(true);
1156                        }
1157                }
1158        },
1159       
1160        handleKey:function(/*Event*/e){
1161                // summary:
1162                //              handle the key for the given event - called by dropdown
1163                //              widgets
1164                if(e.keyCode == dojo.keys.DOWN_ARROW){
1165                        delete this._savedFocus;
1166                        this.focus();
1167                        return false;
1168                }else if(e.keyCode == dojo.keys.ESCAPE){
1169                        this._onCancel();
1170                        return false;
1171                }
1172                return true;
1173        },
1174       
1175        _updateChildClasses: function(){
1176                // summary:
1177                //              Called when a child is added or removed - so that we can
1178                //              update the classes for styling the "current" one differently than
1179                //              the others
1180                var children = this.getChildren();
1181                var length = children.length;
1182                dojo.forEach(children, function(c, idx){
1183                        dojo.toggleClass(c.domNode, "dojoxRollingListPaneCurrentChild", (idx == (length - 1)));
1184                        dojo.toggleClass(c.domNode, "dojoxRollingListPaneCurrentSelected", (idx == (length - 2)));
1185                });
1186        },
1187
1188        startup: function(){
1189                if(this._started){ return; }
1190                if(!this.getParent || !this.getParent()){
1191                        this.resize();
1192                        this.connect(dojo.global, "onresize", "resize");
1193                }
1194                this.connect(this, "addChild", "_updateChildClasses");
1195                this.connect(this, "removeChild", "_updateChildClasses");
1196                this._setStore(this.store);
1197                this.set("showButtons", this.showButtons);
1198                this.inherited(arguments);
1199                this._lastExecutedValue = this.get("value");
1200        },
1201       
1202        getChildItems: function(/*item*/ item){
1203                // summary:
1204                //              Returns the child items for the given store item
1205                var childItems, store = this.store;
1206                dojo.forEach(this.childrenAttrs, function(attr){
1207                        var vals = store.getValues(item, attr);
1208                        if(vals && vals.length){
1209                                childItems = (childItems||[]).concat(vals);
1210                        }
1211                });
1212                return childItems;
1213        },
1214       
1215        getMenuItemForItem: function(/*item*/ item, /* dijit._Contained */ parentPane, /* item[]? */ children){
1216                // summary:
1217                //              user overridable function to return a widget for the given item
1218                //              and its children.
1219                return new dijit.MenuItem({});
1220        },
1221
1222        getPaneForItem: function(/* item? */ item, /* dijit._Contained? */ parentPane, /* item[]? */ children){
1223                // summary:
1224                //              user-overridable function to return a pane that corresponds
1225                //              to the given item in the store.  It can return null to not add a new pane
1226                //              (ie, you are planning on doing something else with it in onItemClick)
1227
1228                // Item is undefined for the root pane, children is undefined for non-group panes
1229                if(!item || children){
1230                        return new dojox.widget._RollingListGroupPane({});
1231                }else{
1232                        return null;
1233                }
1234        },
1235
1236        onItemClick: function(/* item */ item, /* dijit._Contained */ pane, /* item[]? */ children){
1237                // summary:
1238                //              called when an item is clicked - it receives the store item
1239        },
1240       
1241        onExecute: function(){
1242                // summary:
1243                //              exists so that popups don't disappear too soon
1244        },
1245       
1246        onCancel: function(){
1247                // summary:
1248                //              exists so that we can close ourselves if we wish
1249        },
1250       
1251        onChange: function(/* item */ value){
1252                // summary:
1253                //              called when the value of this widget has changed
1254        }
1255       
1256});
Note: See TracBrowser for help on using the repository browser.