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