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