source: Dev/trunk/src/client/dojox/mobile/Carousel.js @ 532

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

Added Dojo 1.9.3 release.

File size: 14.4 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/connect",
4        "dojo/_base/declare",
5        "dojo/_base/event",
6        "dojo/_base/lang",
7        "dojo/sniff",
8        "dojo/dom-class",
9        "dojo/dom-construct",
10        "dojo/dom-style",
11        "dijit/registry",
12        "dijit/_Contained",
13        "dijit/_Container",
14        "dijit/_WidgetBase",
15        "./lazyLoadUtils",
16        "./CarouselItem",
17        "./PageIndicator",
18        "./SwapView",
19        "require",
20        "dojo/has!dojo-bidi?dojox/mobile/bidi/Carousel"
21], function(array, connect, declare, event, lang, has, domClass, domConstruct, domStyle, registry, Contained, Container, WidgetBase, lazyLoadUtils, CarouselItem, PageIndicator, SwapView, require, BidiCarousel){
22
23        // module:
24        //              dojox/mobile/Carousel
25
26        var Carousel = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiCarousel" : "dojox.mobile.Carousel", [WidgetBase, Container, Contained], {
27                // summary:
28                //              A carousel widget that manages a list of images.
29                // description:
30                //              The carousel widget manages a list of images that can be
31                //              displayed horizontally, and allows the user to scroll through
32                //              the list and select a single item.
33                //
34                //              This widget itself has no data store support, but there are two
35                //              subclasses, dojox/mobile/DataCarousel and dojox/mobile/StoreCarousel,
36                //              available for generating the contents from a data store.
37                //              To feed data into a Carousel through a dojo/data, use DataCarousel.
38                //              To feed data into a Carousel through a dojo/store, use StoreCarousel.
39                //
40                //              The Carousel widget loads and instantiates its item contents in
41                //              a lazy manner. For example, if the number of visible items
42                //              (see the property numVisible) is 2, the widget creates 4 items, 2 for the
43                //              initial pane and 2 for the next page, at startup time. If you
44                //              swipe the page to open the second page, the widget creates 2 more
45                //              items for the third page. If the item to create is a dojo widget,
46                //              its module is dynamically loaded automatically before instantiation.
47
48                // numVisible: Number
49                //              The number of visible items.
50                numVisible: 2,
51
52                // itemWidth: Number
53                //              The number of visible items (=numVisible) is determined by
54                //              (carousel_width / itemWidth).
55                //              If itemWidth is specified, numVisible is automatically calculated.
56                //              If resize() is called, numVisible is recalculated and the layout
57                //              is changed accordingly.
58                itemWidth: 0,
59
60                // title: String
61                //              A title of the carousel to be displayed on the title bar.
62                title: "",
63
64                // pageIndicator: [const] Boolean
65                //              If true, a page indicator, a series of small dots that indicate
66                //              the current page, is displayed on the title bar.
67                //              Note that changing the value of the property after the widget
68                //              creation has no effect.
69                pageIndicator: true,
70
71                // navButton: [const] Boolean
72                //              If true, navigation buttons are displyed on the title bar.
73                //              Note that changing the value of the property after the widget
74                //              creation has no effect.
75                navButton: false,
76
77                // height: [const] String
78                //              Explicitly specified height of the widget (ex. "300px"). If
79                //              "inherit" is specified, the height is inherited from its offset
80                //              parent.
81                //              Note that changing the value of the property after the widget
82                //              creation has no effect.
83                height: "",
84
85                // selectable: Boolean
86                //              If true, an item can be selected by clicking it.
87                selectable: true,
88
89                /* internal properties */       
90               
91                // baseClass: String
92                //              The name of the CSS class of this widget.
93                baseClass: "mblCarousel",
94
95                buildRendering: function(){
96                        this.containerNode = domConstruct.create("div", {className: "mblCarouselPages"});
97                        this.inherited(arguments);
98                        var i;
99                        if(this.srcNodeRef){
100                                // reparent
101                                for(i = 0, len = this.srcNodeRef.childNodes.length; i < len; i++){
102                                        this.containerNode.appendChild(this.srcNodeRef.firstChild);
103                                }
104                        }
105
106                        this.headerNode = domConstruct.create("div", {className: "mblCarouselHeaderBar"}, this.domNode);
107
108                        if(this.navButton){
109                                this.btnContainerNode = domConstruct.create("div", {
110                                        className: "mblCarouselBtnContainer"
111                                }, this.headerNode);
112                                domStyle.set(this.btnContainerNode, "float", "right"); // workaround for webkit rendering problem
113                                this.prevBtnNode = domConstruct.create("button", {
114                                        className: "mblCarouselBtn",
115                                        title: "Previous",
116                                        innerHTML: "&lt;"
117                                }, this.btnContainerNode);
118                                this.nextBtnNode = domConstruct.create("button", {
119                                        className: "mblCarouselBtn",
120                                        title: "Next",
121                                        innerHTML: "&gt;"
122                                }, this.btnContainerNode);
123                                this._prevHandle = this.connect(this.prevBtnNode, "onclick", "onPrevBtnClick");
124                                this._nextHandle = this.connect(this.nextBtnNode, "onclick", "onNextBtnClick");
125                        }
126
127                        if(this.pageIndicator){
128                                if(!this.title){
129                                        this.title = "&nbsp;";
130                                }
131                                this.piw = new PageIndicator();
132                                this.headerNode.appendChild(this.piw.domNode);
133                        }
134
135                        this.titleNode = domConstruct.create("div", {
136                                className: "mblCarouselTitle"
137                        }, this.headerNode);
138
139                        this.domNode.appendChild(this.containerNode);
140                        this.subscribe("/dojox/mobile/viewChanged", "handleViewChanged");
141                        this.connect(this.domNode, "onclick", "_onClick");
142                        this.connect(this.domNode, "onkeydown", "_onClick");
143                        this._dragstartHandle = this.connect(this.domNode, "ondragstart", event.stop);
144                        this.selectedItemIndex = -1;
145                        this.items = [];
146                },
147
148                startup: function(){
149                        if(this._started){ return; }
150
151                        var h;
152                        if(this.height === "inherit"){
153                                if(this.domNode.offsetParent){
154                                        h = this.domNode.offsetParent.offsetHeight + "px";
155                                }
156                        }else if(this.height){
157                                h = this.height;
158                        }
159                        if(h){
160                                this.domNode.style.height = h;
161                        }
162
163                        if(this.store){
164                                if(!this.setStore){
165                                        throw new Error("Use StoreCarousel or DataCarousel instead of Carousel.");
166                                }
167                                var store = this.store;
168                                this.store = null;
169                                this.setStore(store, this.query, this.queryOptions);
170                        }else{
171                                this.resizeItems();
172                        }
173                        this.inherited(arguments);
174
175                        this.currentView = array.filter(this.getChildren(), function(view){
176                                return view.isVisible();
177                        })[0];
178                },
179
180                resizeItems: function(){
181                        // summary:
182                        //              Resizes the child items of the carousel.
183                        var idx = 0, i;
184                        var h = this.domNode.offsetHeight - (this.headerNode ? this.headerNode.offsetHeight : 0);
185                        var m = (has("ie") < 10) ? 5 / this.numVisible - 1 : 5 / this.numVisible;
186                        var node, item;
187                        array.forEach(this.getChildren(), function(view){
188                                if(!(view instanceof SwapView)){ return; }
189                                if(!(view.lazy)){
190                                        view._instantiated = true;
191                                }
192                                var ch = view.containerNode.childNodes;
193                                for(i = 0, len = ch.length; i < len; i++){
194                                        node = ch[i];
195                                        if(node.nodeType !== 1){ continue; }
196                                        item = this.items[idx] || {};
197                                        domStyle.set(node, {
198                                                width: item.width || (90 / this.numVisible + "%"),
199                                                height: item.height || h + "px",
200                                                margin: "0 " + (item.margin || m + "%")
201                                        });
202                                        domClass.add(node, "mblCarouselSlot");
203                                        idx++;
204                                }
205                        }, this);
206
207                        if(this.piw){
208                                this.piw.refId = this.containerNode.firstChild;
209                                this.piw.reset();
210                        }
211                },
212
213                resize: function(){
214                        if(!this.itemWidth){ return; }
215                        var num = Math.floor(this.domNode.offsetWidth / this.itemWidth);
216                        if(num === this.numVisible){ return; }
217                        this.selectedItemIndex = this.getIndexByItemWidget(this.selectedItem);
218                        this.numVisible = num;
219                        if(this.items.length > 0){
220                                this.onComplete(this.items);
221                                this.select(this.selectedItemIndex);
222                        }
223                },
224
225                fillPages: function(){
226                        array.forEach(this.getChildren(), function(child, i){
227                                var s = "";
228                                var j;
229                                for(j = 0; j < this.numVisible; j++){
230                                        var type, props = "", mixins;
231                                        var idx = i * this.numVisible + j;
232                                        var item = {};
233                                        if(idx < this.items.length){
234                                                item = this.items[idx];
235                                                type = this.store.getValue(item, "type");
236                                                if(type){
237                                                        props = this.store.getValue(item, "props");
238                                                        mixins = this.store.getValue(item, "mixins");
239                                                }else{
240                                                        type = "dojox.mobile.CarouselItem";
241                                                        array.forEach(["alt", "src", "headerText", "footerText"], function(p){
242                                                                var v = this.store.getValue(item, p);
243                                                                if(v !== undefined){
244                                                                        if(props){ props += ','; }
245                                                                        props += p + ':"' + v + '"';
246                                                                }
247                                                        }, this);
248                                                }
249                                        }else{
250                                                type = "dojox.mobile.CarouselItem";
251                                                props = 'src:"' + require.toUrl("dojo/resources/blank.gif") + '"' +
252                                                        ', className:"mblCarouselItemBlank"';
253                                        }
254
255                                        s += '<div data-dojo-type="' + type + '"';
256                                        if(props){
257                                                s += ' data-dojo-props=\'' + props + '\'';
258                                        }
259                                        if(mixins){
260                                                s += ' data-dojo-mixins=\'' + mixins + '\'';
261                                        }
262                                        s += '></div>';
263                                }
264                                child.containerNode.innerHTML = s;
265                        }, this);
266                },
267
268                onComplete: function(/*Array*/items){
269                        // summary:
270                        //              A handler that is called after the fetch completes.
271                        array.forEach(this.getChildren(), function(child){
272                                if(child instanceof SwapView){
273                                        child.destroyRecursive();
274                                }
275                        });
276                        this.selectedItem = null;
277                        this.items = items;
278                        var nPages = Math.ceil(items.length / this.numVisible),
279                                i, h = this.domNode.offsetHeight - this.headerNode.offsetHeight,
280                                idx = this.selectedItemIndex === -1 ? 0 : this.selectedItemIndex;
281                                pg = Math.floor(idx / this.numVisible); // current page
282                        for(i = 0; i < nPages; i++){
283                                var w = new SwapView({height: h + "px", lazy:true});
284                                this.addChild(w);
285                                if(i === pg){
286                                        w.show();
287                                        this.currentView = w;
288                                }else{
289                                        w.hide();
290                                }
291                        }
292                        this.fillPages();
293                        this.resizeItems();
294                        var children = this.getChildren();
295                        var from = pg - 1 < 0 ? 0 : pg - 1;
296                        var to = pg + 1 > nPages - 1 ? nPages - 1 : pg + 1;
297                        for(i = from; i <= to; i++){
298                                this.instantiateView(children[i]);
299                        }
300                },
301
302                onError: function(/*String*/ /*===== errText =====*/){
303                        // summary:
304                        //              An error handler.
305                },
306
307                onUpdate: function(/*Object*/ /*===== item, =====*/ /*Number*/ /*===== insertedInto =====*/){
308                        // summary:
309                        //              Adds a new item or updates an existing item.
310                },
311
312                onDelete: function(/*Object*/ /*===== item, =====*/ /*Number*/ /*===== removedFrom =====*/){
313                        // summary:
314                        //              Deletes an existing item.
315                },
316
317                onSet: function(item, attribute, oldValue, newValue){
318                },
319
320                onNew: function(newItem, parentInfo){
321                },
322
323                onStoreClose: function(request){
324                        // summary:
325                        //              Called when the store is closed.
326                },
327
328                getParentView: function(/*DomNode*/node){
329                        // summary:
330                        //              Returns the parent view of the given DOM node.
331                        var w;
332                        for(w = registry.getEnclosingWidget(node); w; w = w.getParent()){
333                                if(w.getParent() instanceof SwapView){ return w; }
334                        }
335                        return null;
336                },
337
338                getIndexByItemWidget: function(/*Widget*/w){
339                        // summary:
340                        //              Returns the index of a given item widget.
341                        if(!w){ return -1; }
342                        var view = w.getParent();
343                        return array.indexOf(this.getChildren(), view) * this.numVisible +
344                                array.indexOf(view.getChildren(), w);
345                },
346
347                getItemWidgetByIndex: function(/*Number*/index){
348                        // summary:
349                        //              Returns the index of an item widget at a given index.
350                        if(index === -1){ return null; }
351                        var view = this.getChildren()[Math.floor(index / this.numVisible)];
352                        return view.getChildren()[index % this.numVisible];
353                },
354
355                onPrevBtnClick: function(/*Event*/ /*===== e =====*/){
356                        // summary:
357                        //              Called when the "previous" button is clicked.
358                        if(this.currentView){
359                                this.currentView.goTo(-1);
360                        }
361                },
362
363                onNextBtnClick: function(/*Event*/ /*===== e =====*/){
364                        // summary:
365                        //              Called when the "next" button is clicked.
366                        if(this.currentView){
367                                this.currentView.goTo(1);
368                        }
369                },
370
371                _onClick: function(e){
372                        // summary:
373                        //              Internal handler for click events.
374                        // tags:
375                        //              private
376                        if(this.onClick(e) === false){ return; } // user's click action
377                        if(e && e.type === "keydown"){ // keyboard navigation for accessibility
378                                if(e.keyCode === 39){ // right arrow
379                                        this.onNextBtnClick();
380                                }else if(e.keyCode === 37){ // left arrow
381                                        this.onPrevBtnClick();
382                                }else if(e.keyCode !== 13){ // !Enter
383                                        return;
384                                }
385                        }
386
387                        var w;
388                        for(w = registry.getEnclosingWidget(e.target); ; w = w.getParent()){
389                                if(!w){ return; }
390                                if(w.getParent() instanceof SwapView){ break; }
391                        }
392                        this.select(w);
393                        var idx = this.getIndexByItemWidget(w);
394                        connect.publish("/dojox/mobile/carouselSelect", [this, w, this.items[idx], idx]);
395                },
396
397                select: function(/*Widget|Number*/itemWidget){
398                        // summary:
399                        //              Selects the given widget.
400                        if(typeof(itemWidget) === "number"){
401                                itemWidget = this.getItemWidgetByIndex(itemWidget);
402                        }
403                        if(this.selectable){
404                                if(this.selectedItem){
405                                        this.selectedItem.set("selected", false);
406                                        domClass.remove(this.selectedItem.domNode, "mblCarouselSlotSelected");
407                                }
408                                if(itemWidget){
409                                        itemWidget.set("selected", true);
410                                        domClass.add(itemWidget.domNode, "mblCarouselSlotSelected");
411                                }
412                                this.selectedItem = itemWidget;
413                        }
414                },
415
416                onClick: function(/*Event*/ /*===== e =====*/){
417                        // summary:
418                        //              User-defined function to handle clicks.
419                        // tags:
420                        //              callback
421                },
422
423                instantiateView: function(view){
424                        // summary:
425                        //              Instantiates the given view.
426                        if(view && !view._instantiated){
427                                var isHidden = (domStyle.get(view.domNode, "display") === "none");
428                                if(isHidden){
429                                        domStyle.set(view.domNode, {visibility:"hidden", display:""});
430                                }
431                                lazyLoadUtils.instantiateLazyWidgets(view.containerNode, null, function(root){
432                                        if(isHidden){
433                                                domStyle.set(view.domNode, {visibility:"visible", display:"none"});
434                                        }
435                                });
436                                view._instantiated = true;
437                        }
438                },
439
440                handleViewChanged: function(view){
441                        // summary:
442                        //              Listens to "/dojox/mobile/viewChanged" events.
443                        if(view.getParent() !== this){ return; }
444                        if(this.currentView.nextView(this.currentView.domNode) === view){
445                                this.instantiateView(view.nextView(view.domNode));
446                        }else{
447                                this.instantiateView(view.previousView(view.domNode));
448                        }
449                        this.currentView = view;
450                },
451
452                _setTitleAttr: function(/*String*/title){
453                        // tags:
454                        //              private
455                        this.titleNode.innerHTML = this._cv ? this._cv(title) : title;
456                        this._set("title", title);
457                }
458        });
459       
460        Carousel.ChildSwapViewProperties = {
461                // summary:
462                //              This property can be specified for the SwapView children of a dojox/mobile/Carousel.
463
464                // lazy: Boolean
465                //              Specifies that the Carousel child must be lazily loaded.
466                lazy: false
467        };
468
469        // Since any widget can be specified as an Accordion child, mix ChildWidgetProperties
470        // into the base widget class.  (This is a hack, but it's effective.)
471        // This is for the benefit of the parser. Remove for 2.0.  Also, hide from doc viewer.
472        lang.extend(SwapView, /*===== {} || =====*/ Carousel.ChildSwapViewProperties);
473       
474        return has("dojo-bidi") ? declare("dojox.mobile.Carousel", [Carousel, BidiCarousel]) : Carousel;
475});
Note: See TracBrowser for help on using the repository browser.