1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * The list view control of Cocos UI.
 28  * @class
 29  * @extends ccui.ScrollView
 30  * @example
 31  * var listView = new ccui.ListView();
 32  * // set list view ex direction
 33  * listView.setDirection(ccui.ScrollView.DIR_VERTICAL);
 34  * listView.setTouchEnabled(true);
 35  * listView.setBounceEnabled(true);
 36  * listView.setBackGroundImage("res/cocosui/green_edit.png");
 37  * listView.setBackGroundImageScale9Enabled(true);
 38  * listView.setContentSize(cc.size(240, 130));
 39  * this.addChild(listView);
 40  */
 41 ccui.ListView = ccui.ScrollView.extend(/** @lends ccui.ListView# */{
 42     _model: null,
 43     _items: null,
 44     _gravity: null,
 45     _itemsMargin: 0,
 46 
 47     _curSelectedIndex: 0,
 48     _refreshViewDirty: true,
 49 
 50     _listViewEventListener: null,
 51     _listViewEventSelector: null,
 52     /**
 53      * allocates and initializes a UIListView.
 54      * Constructor of ccui.ListView, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
 55      * @example
 56      * // example
 57      * var aListView = new ccui.ListView();
 58      */
 59     ctor: function () {
 60         ccui.ScrollView.prototype.ctor.call(this);
 61         this._items = [];
 62         this._gravity = ccui.ListView.GRAVITY_CENTER_HORIZONTAL;
 63         this.setTouchEnabled(true);
 64 
 65         this.init();
 66     },
 67 
 68     /**
 69      * Initializes a ccui.ListView. Please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
 70      * @returns {boolean}
 71      * @override
 72      */
 73     init: function () {
 74         if (ccui.ScrollView.prototype.init.call(this)) {
 75             this.setLayoutType(ccui.Layout.LINEAR_VERTICAL);
 76             return true;
 77         }
 78         return false;
 79     },
 80 
 81     /**
 82      * Sets a item model for ListView. A model will be cloned for adding default item.
 83      * @param {ccui.Widget} model
 84      */
 85     setItemModel: function (model) {
 86         if (!model)
 87             return;
 88         this._model = model;
 89     },
 90 
 91     _updateInnerContainerSize: function () {
 92         var locItems = this._items, length, i;
 93         switch (this.direction) {
 94             case ccui.ScrollView.DIR_VERTICAL:
 95                 length = locItems.length;
 96                 var totalHeight = (length - 1) * this._itemsMargin;
 97                 for (i = 0; i < length; i++) {
 98                     totalHeight += locItems[i].getContentSize().height;
 99                 }
100                 this.setInnerContainerSize(cc.size(this._contentSize.width, totalHeight));
101                 break;
102             case ccui.ScrollView.DIR_HORIZONTAL:
103                 length = locItems.length;
104                 var totalWidth = (length - 1) * this._itemsMargin;
105                 for (i = 0; i < length; i++) {
106                     totalWidth += locItems[i].getContentSize().width;
107                 }
108                 this.setInnerContainerSize(cc.size(totalWidth, this._contentSize.height));
109                 break;
110             default:
111                 break;
112         }
113     },
114 
115     _remedyLayoutParameter: function (item) {
116         if (!item)
117             return;
118         var llp;
119         switch (this.direction) {
120             case ccui.ScrollView.DIR_VERTICAL:
121                 llp = item.getLayoutParameter();
122                 if (!llp) {
123                     var defaultLp = ccui.LinearLayoutParameter.create();
124                     switch (this._gravity) {
125                         case ccui.ListView.GRAVITY_LEFT:
126                             defaultLp.setGravity(ccui.LinearLayoutParameter.LEFT);
127                             break;
128                         case ccui.ListView.GRAVITY_RIGHT:
129                             defaultLp.setGravity(ccui.LinearLayoutParameter.RIGHT);
130                             break;
131                         case ccui.ListView.GRAVITY_CENTER_HORIZONTAL:
132                             defaultLp.setGravity(ccui.LinearLayoutParameter.CENTER_HORIZONTAL);
133                             break;
134                         default:
135                             break;
136                     }
137                     if (this.getIndex(item) == 0)
138                         defaultLp.setMargin(ccui.MarginZero());
139                     else
140                         defaultLp.setMargin(new ccui.Margin(0.0, this._itemsMargin, 0.0, 0.0));
141                     item.setLayoutParameter(defaultLp);
142                 } else {
143                     if (this.getIndex(item) == 0)
144                         llp.setMargin(ccui.MarginZero());
145                     else
146                         llp.setMargin(new ccui.Margin(0, this._itemsMargin, 0, 0));
147                     switch (this._gravity) {
148                         case ccui.ListView.GRAVITY_LEFT:
149                             llp.setGravity(ccui.LinearLayoutParameter.LEFT);
150                             break;
151                         case ccui.ListView.GRAVITY_RIGHT:
152                             llp.setGravity(ccui.LinearLayoutParameter.RIGHT);
153                             break;
154                         case ccui.ListView.GRAVITY_CENTER_HORIZONTAL:
155                             llp.setGravity(ccui.LinearLayoutParameter.CENTER_HORIZONTAL);
156                             break;
157                         default:
158                             break;
159                     }
160                 }
161                 break;
162             case ccui.ScrollView.DIR_HORIZONTAL:
163                 llp = item.getLayoutParameter();
164                 if (!llp) {
165                     var defaultLp = ccui.LinearLayoutParameter.create();
166                     switch (this._gravity) {
167                         case ccui.ListView.GRAVITY_TOP:
168                             defaultLp.setGravity(ccui.LinearLayoutParameter.TOP);
169                             break;
170                         case ccui.ListView.GRAVITY_BOTTOM:
171                             defaultLp.setGravity(ccui.LinearLayoutParameter.BOTTOM );
172                             break;
173                         case ccui.ListView.GRAVITY_CENTER_VERTICAL:
174                             defaultLp.setGravity(ccui.LinearLayoutParameter.CENTER_VERTICAL);
175                             break;
176                         default:
177                             break;
178                     }
179                     if (this.getIndex(item) == 0)
180                         defaultLp.setMargin(ccui.MarginZero());
181                     else
182                         defaultLp.setMargin(new ccui.Margin(this._itemsMargin, 0.0, 0.0, 0.0));
183                     item.setLayoutParameter(defaultLp);
184                 } else {
185                     if (this.getIndex(item) == 0)
186                         llp.setMargin(ccui.MarginZero());
187                     else
188                         llp.setMargin(new ccui.Margin(this._itemsMargin, 0.0, 0.0, 0.0));
189                     switch (this._gravity) {
190                         case ccui.ListView.GRAVITY_TOP:
191                             llp.setGravity(ccui.LinearLayoutParameter.TOP);
192                             break;
193                         case ccui.ListView.GRAVITY_BOTTOM:
194                             llp.setGravity(ccui.LinearLayoutParameter.BOTTOM);
195                             break;
196                         case ccui.ListView.GRAVITY_CENTER_VERTICAL:
197                             llp.setGravity(ccui.LinearLayoutParameter.CENTER_VERTICAL);
198                             break;
199                         default:
200                             break;
201                     }
202                 }
203                 break;
204             default:
205                 break;
206         }
207     },
208 
209     /**
210      * Push back a default item(create by a cloned model) into ListView.
211      */
212     pushBackDefaultItem: function () {
213         if (!this._model)
214             return;
215         var newItem = this._model.clone();
216         this._remedyLayoutParameter(newItem);
217         this.addChild(newItem);
218         this._refreshViewDirty = true;
219     },
220 
221     /**
222      * Insert a default item(create by a cloned model) into ListView.
223      * @param {Number} index
224      */
225     insertDefaultItem: function (index) {
226         if (!this._model)
227             return;
228         var newItem = this._model.clone();
229         this._items.splice(index, 0, newItem);
230         ccui.ScrollView.prototype.addChild.call(this, newItem);
231         this._remedyLayoutParameter(newItem);
232 
233         this._refreshViewDirty = true;
234     },
235 
236     /**
237      * Push back custom item into ListView.
238      * @param {ccui.Widget} item
239      */
240     pushBackCustomItem: function (item) {
241         this._remedyLayoutParameter(item);
242         this.addChild(item);
243         this._refreshViewDirty = true;
244     },
245 
246     /**
247      * add child to ListView
248      * @override
249      * @param {cc.Node} widget
250      * @param {Number} [zOrder]
251      * @param {Number|String} [tag]  tag or name
252      */
253     addChild: function (widget, zOrder, tag) {
254         if (widget) {
255             zOrder = zOrder || widget.getLocalZOrder();
256             tag = tag || widget.getName();
257             ccui.ScrollView.prototype.addChild.call(this, widget, zOrder, tag);
258             if(widget instanceof ccui.Widget)
259                 this._items.push(widget);
260         }
261     },
262 
263     /**
264      * remove child from ListView
265      * @override
266      * @param {cc.Node} widget
267      * @param {Boolean} [cleanup=true]
268      */
269     removeChild: function(widget, cleanup){
270         if (widget) {
271             var index = this._items.indexOf(widget);
272             if(index > -1)
273                 this._items.splice(index, 1);
274             ccui.ScrollView.prototype.removeChild.call(this, widget, cleanup);
275         }
276     },
277 
278     /**
279      * Removes all children from ccui.ListView.
280      */
281     removeAllChildren: function(){
282         this.removeAllChildrenWithCleanup(true);
283     },
284 
285     /**
286      * Removes all children from ccui.ListView and do a cleanup all running actions depending on the cleanup parameter.
287      * @param {Boolean} cleanup
288      */
289     removeAllChildrenWithCleanup: function(cleanup){
290         ccui.ScrollView.prototype.removeAllChildrenWithCleanup.call(this, cleanup);
291         this._items = [];
292     },
293 
294     /**
295      * Push back custom item into ccui.ListView.
296      * @param {ccui.Widget} item
297      * @param {Number} index
298      */
299     insertCustomItem: function (item, index) {
300         this._items.splice(index, 0, item);
301         ccui.ScrollView.prototype.addChild.call(this, item);
302         this._remedyLayoutParameter(item);
303         this._refreshViewDirty = true;
304     },
305 
306     /**
307      * Removes a item whose index is same as the parameter.
308      * @param {Number} index
309      */
310     removeItem: function (index) {
311         var item = this.getItem(index);
312         if (!item)
313             return;
314         this.removeChild(item, true);
315         this._refreshViewDirty = true;
316     },
317 
318     /**
319      * Removes the last item of ccui.ListView.
320      */
321     removeLastItem: function () {
322         this.removeItem(this._items.length - 1);
323     },
324 
325     /**
326      * Removes all items from ccui.ListView.
327      */
328     removeAllItems: function(){
329         this.removeAllChildren();
330     },
331 
332     /**
333      * Returns a item whose index is same as the parameter.
334      * @param {Number} index
335      * @returns {ccui.Widget}
336      */
337     getItem: function (index) {
338         if (index < 0 || index >= this._items.length)
339             return null;
340         return this._items[index];
341     },
342 
343     /**
344      * Returns the item container.
345      * @returns {Array}
346      */
347     getItems: function () {
348         return this._items;
349     },
350 
351     /**
352      * Returns the index of item.
353      * @param {ccui.Widget} item the item which need to be checked.
354      * @returns {Number} the index of item.
355      */
356     getIndex: function (item) {
357         return this._items.indexOf(item);
358     },
359 
360     /**
361      * Changes the gravity of ListView.
362      * @param {ccui.ListView.GRAVITY_LEFT|ccui.ListView.GRAVITY_RIGHT|ccui.ListView.GRAVITY_CENTER_HORIZONTAL|ccui.ListView.GRAVITY_BOTTOM|ccui.ListView.GRAVITY_CENTER_VERTICAL} gravity
363      */
364     setGravity: function (gravity) {
365         if (this._gravity == gravity)
366             return;
367         this._gravity = gravity;
368         this._refreshViewDirty = true;
369     },
370 
371     /**
372      * Changes the margin between each item.
373      * @param {Number} margin
374      */
375     setItemsMargin: function (margin) {
376         if (this._itemsMargin == margin)
377             return;
378         this._itemsMargin = margin;
379         this._refreshViewDirty = true;
380     },
381 
382     /**
383      * Returns the margin between each item.
384      * @returns {Number}
385      */
386     getItemsMargin:function(){
387         return this._itemsMargin;
388     },
389 
390     /**
391      * Changes scroll direction of ccui.ListView.
392      * @param {ccui.ScrollView.DIR_NONE | ccui.ScrollView.DIR_VERTICAL | ccui.ScrollView.DIR_HORIZONTAL | ccui.ScrollView.DIR_BOTH} dir
393      */
394     setDirection: function (dir) {
395         switch (dir) {
396             case ccui.ScrollView.DIR_VERTICAL:
397                 this.setLayoutType(ccui.Layout.LINEAR_VERTICAL);
398                 break;
399             case ccui.ScrollView.DIR_HORIZONTAL:
400                 this.setLayoutType(ccui.Layout.LINEAR_HORIZONTAL);
401                 break;
402             case ccui.ScrollView.DIR_BOTH:
403                 return;
404             default:
405                 return;
406                 break;
407         }
408         ccui.ScrollView.prototype.setDirection.call(this, dir);
409     },
410 
411     /**
412      * Requests refresh list view.
413      */
414     requestRefreshView: function () {
415         this._refreshViewDirty = true;
416     },
417 
418     /**
419      * Refreshes list view.
420      */
421     refreshView: function () {
422         var locItems = this._items;
423         for (var i = 0; i < locItems.length; i++) {
424             var item = locItems[i];
425             item.setLocalZOrder(i);
426             this._remedyLayoutParameter(item);
427         }
428         this._updateInnerContainerSize();
429     },
430 
431     /**
432      * provides a public _doLayout function for Editor. it calls _doLayout.
433      */
434     doLayout: function(){
435         this._doLayout();
436     },
437 
438     _doLayout: function(){
439         ccui.Layout.prototype._doLayout.call(this);
440         if (this._refreshViewDirty) {
441             this.refreshView();
442             this._refreshViewDirty = false;
443         }
444     },
445 
446     /**
447      * Adds event listener to ccui.ListView.
448      * @param {Function} selector
449      * @param {Object} [target=]
450      * @deprecated since v3.0, please use addEventListener instead.
451      */
452     addEventListenerListView: function (selector, target) {
453         this.addEventListener(selector, target);
454     },
455 
456     /**
457      * Adds event listener to ccui.ListView.
458      * @param {Function} selector
459      * @param {Object} [target=]
460      */
461     addEventListener: function(selector, target){
462         this._listViewEventListener = target;
463         this._listViewEventSelector = selector;
464     },
465 
466     _selectedItemEvent: function (event) {
467         var eventEnum = (event == ccui.Widget.TOUCH_BEGAN) ? ccui.ListView.ON_SELECTED_ITEM_START : ccui.ListView.ON_SELECTED_ITEM_END;
468         if(this._listViewEventSelector){
469             if (this._listViewEventListener)
470                 this._listViewEventSelector.call(this._listViewEventListener, this, eventEnum);
471             else
472                 this._listViewEventSelector(this, eventEnum);
473         }
474     },
475 
476     /**
477      * Intercept touch event, handle its child's touch event.
478      * @param {Number} eventType
479      * @param {ccui.Widget} sender
480      * @param {cc.Touch} touch
481      */
482     interceptTouchEvent: function (eventType, sender, touch) {
483         ccui.ScrollView.prototype.interceptTouchEvent.call(this, eventType, sender, touch);
484         if (eventType != ccui.Widget.TOUCH_MOVED) {
485             var parent = sender;
486             while (parent) {
487                 if (parent && parent.getParent() == this._innerContainer) {
488                     this._curSelectedIndex = this.getIndex(parent);
489                     break;
490                 }
491                 parent = parent.getParent();
492             }
493             if (sender.isHighlighted())
494                 this._selectedItemEvent(eventType);
495         }
496     },
497 
498     /**
499      * Returns current selected index
500      * @returns {number}
501      */
502     getCurSelectedIndex: function () {
503         return this._curSelectedIndex;
504     },
505 
506     _onSizeChanged: function () {
507         ccui.ScrollView.prototype._onSizeChanged.call(this);
508         this._refreshViewDirty = true;
509     },
510 
511     /**
512      * Returns the "class name" of ccui.ListView.
513      * @returns {string}
514      */
515     getDescription: function () {
516         return "ListView";
517     },
518 
519     _createCloneInstance: function () {
520         return ccui.ListView.create();
521     },
522 
523     _copyClonedWidgetChildren: function (model) {
524         var arrayItems = model.getItems();
525         for (var i = 0; i < arrayItems.length; i++) {
526             var item = arrayItems[i];
527             this.pushBackCustomItem(item.clone());
528         }
529     },
530 
531     _copySpecialProperties: function (listView) {
532         if(listView instanceof ccui.ListView){
533             ccui.ScrollView.prototype._copySpecialProperties.call(this, listView);
534             this.setItemModel(listView._model);
535             this.setItemsMargin(listView._itemsMargin);
536             this.setGravity(listView._gravity);
537 
538             this._listViewEventListener = listView._listViewEventListener;
539             this._listViewEventSelector = listView._listViewEventSelector;
540         }
541     }
542 });
543 
544 /**
545  * allocates and initializes a UIListView.
546  * @deprecated since v3.0, please use new ccui.ListView() instead.
547  * @example
548  * // example
549  * var uiPageView = ccui.ListView.create();
550  */
551 ccui.ListView.create = function () {
552     return new ccui.ListView();
553 };
554 
555 // Constants
556 //listView event type
557 /**
558  * The flag selected item of ccui.ListView's event.
559  * @constant
560  * @type {number}
561  */
562 ccui.ListView.EVENT_SELECTED_ITEM = 0;
563 
564 /**
565  * The flag selected item start of ccui.ListView's event.
566  * @constant
567  * @type {number}
568  */
569 ccui.ListView.ON_SELECTED_ITEM_START = 0;
570 /**
571  * The flag selected item end of ccui.ListView's event.
572  * @constant
573  * @type {number}
574  */
575 ccui.ListView.ON_SELECTED_ITEM_END = 1;
576 
577 //listView gravity
578 /**
579  * The left flag of ccui.ListView's gravity.
580  * @constant
581  * @type {number}
582  */
583 ccui.ListView.GRAVITY_LEFT = 0;
584 /**
585  * The right flag of ccui.ListView's gravity.
586  * @constant
587  * @type {number}
588  */
589 ccui.ListView.GRAVITY_RIGHT = 1;
590 /**
591  * The center horizontal flag of ccui.ListView's gravity.
592  * @constant
593  * @type {number}
594  */
595 ccui.ListView.GRAVITY_CENTER_HORIZONTAL = 2;
596 /**
597  * The top flag of ccui.ListView's gravity.
598  * @constant
599  * @type {number}
600  */
601 ccui.ListView.GRAVITY_TOP = 3;
602 /**
603  * The bottom flag of ccui.ListView's gravity.
604  * @constant
605  * @type {number}
606  */
607 ccui.ListView.GRAVITY_BOTTOM = 4;
608 /**
609  * The center vertical flag of ccui.ListView's gravity.
610  * @constant
611  * @type {number}
612  */
613 ccui.ListView.GRAVITY_CENTER_VERTICAL = 5;