1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @class Container is the base class to all container classes. Each Container can add other view object as children.
  9  * @augments View
 10  * @param {Object} properties Properties parameters of the object to create. Contains all writable properties of this class.
 11  * @module hilo/view/Container
 12  * @requires hilo/core/Hilo
 13  * @requires hilo/core/Class
 14  * @requires hilo/view/View
 15  * @property {Array} children List of children elements of the container, readonly!
 16  * @property {Boolean} pointerChildren Whether children elements of the container can response to user interactive events, default value is true.
 17  * @property {Boolean} clipChildren Whether clip children elements which are out of the container, default value is false.
 18  */
 19 var Container = Class.create(/** @lends Container.prototype */{
 20     Extends: View,
 21     constructor: function(properties){
 22         properties = properties || {};
 23         this.id = this.id || properties.id || Hilo.getUid("Container");
 24         Container.superclass.constructor.call(this, properties);
 25 
 26         if(this.children) this._updateChildren();
 27         else this.children = [];
 28     },
 29 
 30     children: null,
 31     pointerChildren: true,
 32     clipChildren: false,
 33 
 34     /**
 35      * Return the amount of the children elements of the container.
 36      * @returns {Uint} The amount of the children elements of the container.
 37      */
 38     getNumChildren: function(){
 39         return this.children.length;
 40     },
 41 
 42     /**
 43      * Add child element at given index.
 44      * @param {View} child Element to add.
 45      * @param {Number} index The given index position, range from 0.
 46      */
 47     addChildAt: function(child, index){
 48         var children = this.children,
 49             len = children.length,
 50             parent = child.parent;
 51 
 52         index = index < 0 ? 0 : index > len ? len : index;
 53         var childIndex = this.getChildIndex(child);
 54         if(childIndex == index){
 55             return this;
 56         }else if(childIndex >= 0){
 57             children.splice(childIndex, 1);
 58             index = index == len ? len - 1 : index;
 59         }else if(parent){
 60             parent.removeChild(child);
 61         }
 62 
 63         children.splice(index, 0, child);
 64 
 65         //直接插入,影响插入位置之后的深度
 66         //Insert directly, this will affect depth of elements after the index.
 67         if(childIndex < 0){
 68             this._updateChildren(index);
 69         }
 70         //只是移动时影响中间段的深度
 71         //Will affect depth of elements in the middle during moving
 72         else{
 73             var startIndex = childIndex < index ? childIndex : index;
 74             var endIndex = childIndex < index ? index : childIndex;
 75             this._updateChildren(startIndex, endIndex + 1);
 76         }
 77 
 78         return this;
 79     },
 80 
 81     /**
 82      * Add child element at the top.
 83      * @param {View} child Elements to add.
 84      */
 85     addChild: function(child){
 86         var total = this.children.length,
 87             args = arguments;
 88 
 89         for(var i = 0, len = args.length; i < len; i++){
 90             this.addChildAt(args[i], total + i);
 91         }
 92         return this;
 93     },
 94 
 95     /**
 96      * Remove element at the index.
 97      * @param {Int} index Index of the element to remove, range from 0.
 98      * @returns {View} Element had been removed.
 99      */
100     removeChildAt: function(index){
101         var children = this.children;
102         if(index < 0 || index >= children.length) return null;
103 
104         var child = children[index];
105         if(child){
106             //NOTE: use `__renderer` for fixing child removal (DOMRenderer and FlashRenderer only).
107             //Do `not` use it in any other case.
108             if(!child.__renderer){
109                 var obj = child;
110                 while(obj = obj.parent){
111                     //obj is stage
112                     if(obj.renderer){
113                         child.__renderer = obj.renderer;
114                         break;
115                     }
116                     else if(obj.__renderer){
117                         child.__renderer = obj.__renderer;
118                         break;
119                     }
120                 }
121             }
122 
123             if(child.__renderer){
124                 child.__renderer.remove(child);
125             }
126 
127             child.parent = null;
128             child.depth = -1;
129         }
130 
131         children.splice(index, 1);
132         this._updateChildren(index);
133 
134         return child;
135     },
136 
137     /**
138      * Remove the given child element.
139      * @param {View} child The child element to remove.
140      * @returns {View} Element had been removed.
141      */
142     removeChild: function(child){
143         return this.removeChildAt(this.getChildIndex(child));
144     },
145 
146     /**
147      * Remove child element by its id.
148      * @param {String} id The id of element to remove.
149      * @returns {View} Element had been removed.
150      */
151     removeChildById: function(id){
152         var children = this.children, child;
153         for(var i = 0, len = children.length; i < len; i++){
154             child = children[i];
155             if(child.id === id){
156                 this.removeChildAt(i);
157                 return child;
158             }
159         }
160         return null;
161     },
162 
163     /**
164      * Remove all children elements.
165      * @returns {Container} Container itself.
166      */
167     removeAllChildren: function(){
168         while(this.children.length) this.removeChildAt(0);
169         return this;
170     },
171 
172     /**
173      * Return child element at the given index.
174      * @param {Number} index The index of the element, range from 0.
175      */
176     getChildAt: function(index){
177         var children = this.children;
178         if(index < 0 || index >= children.length) return null;
179         return children[index];
180     },
181 
182     /**
183      * Return child element at the given id.
184      * @param {String} id The id of child element to return.
185      */
186     getChildById: function(id){
187         var children = this.children, child;
188         for(var i = 0, len = children.length; i < len; i++){
189             child = children[i];
190             if(child.id === id) return child;
191         }
192         return null;
193     },
194 
195     /**
196      * Return index value of the given child element.
197      * @param {View} child The child element need to get its index.
198      */
199     getChildIndex: function(child){
200         return this.children.indexOf(child);
201     },
202 
203     /**
204      * Set the index of child element.
205      * @param {View} child The child element need to set index.
206      * @param {Number} index The index to set to the element.
207      */
208     setChildIndex: function(child, index){
209         var children = this.children,
210             oldIndex = children.indexOf(child);
211 
212         if(oldIndex >= 0 && oldIndex != index){
213             var len = children.length;
214             index = index < 0 ? 0 : index >= len ? len - 1 : index;
215             children.splice(oldIndex, 1);
216             children.splice(index, 0, child);
217             this._updateChildren();
218         }
219         return this;
220     },
221 
222     /**
223      * Swap index between two child elements.
224      * @param {View} child1 Child element A.
225      * @param {View} child2 Child element B.
226      */
227     swapChildren: function(child1, child2){
228         var children = this.children,
229             index1 = this.getChildIndex(child1),
230             index2 = this.getChildIndex(child2);
231 
232         child1.depth = index2;
233         children[index2] = child1;
234         child2.depth = index1;
235         children[index1] = child2;
236     },
237 
238     /**
239      * Swap two children elements at given indexes.
240      * @param {Number} index1 Given index A.
241      * @param {Number} index2 Given index B.
242      */
243     swapChildrenAt: function(index1, index2){
244         var children = this.children,
245             child1 = this.getChildAt(index1),
246             child2 = this.getChildAt(index2);
247 
248         child1.depth = index2;
249         children[index2] = child1;
250         child2.depth = index1;
251         children[index1] = child2;
252     },
253 
254     /**
255      * Sort children elements by the given key or function.
256      * @param {Object} keyOrFunction If is String, sort children elements by the given property string; If is Function, sort by the function.
257      */
258     sortChildren: function(keyOrFunction){
259         var fn = keyOrFunction,
260             children = this.children;
261         if(typeof fn == "string"){
262             var key = fn;
263             fn = function(a, b){
264                 return b[key] - a[key];
265             };
266         }
267         children.sort(fn);
268         this._updateChildren();
269     },
270 
271     /**
272      * Update children elements.
273      * @private
274      */
275     _updateChildren: function(start, end){
276         var children = this.children, child;
277         start = start || 0;
278         end = end || children.length;
279         for(var i = start; i < end; i++){
280             child = children[i];
281             child.depth = i + 1;
282             child.parent = this;
283         }
284     },
285 
286     /**
287      * Return whether this container contains the parameter described child element.
288      * @param {View} child The child element to test.
289      */
290     contains: function(child){
291         while(child = child.parent){
292             if(child === this){
293                 return true;
294             }
295         }
296         return false;
297     },
298 
299     /**
300      * Return object at the point positioned by given values on x axis and y axis.
301      * @param {Number} x The point's value on the coordinate's x axis.
302      * @param {Number} y The point's value on the coordinate's y asix.
303      * @param {Boolean} usePolyCollision Whether use polygon collision detection, default value is false.
304      * @param {Boolean} global Whether return all elements that match the condition, default value is false.
305      * @param {Boolean} eventMode Whether find elements under event mode, default value is false.
306      */
307     getViewAtPoint: function(x, y, usePolyCollision, global, eventMode){
308         var result = global ? [] : null,
309             children = this.children, child, obj;
310 
311         for(var i = children.length - 1; i >= 0; i--){
312             child = children[i];
313             //skip child which is not shown or pointer enabled
314             if(!child || !child.visible || child.alpha <= 0 || (eventMode && !child.pointerEnabled)) continue;
315             //find child recursively
316             if(child.children && child.children.length && !(eventMode && !child.pointerChildren)){
317                 obj = child.getViewAtPoint(x, y, usePolyCollision, global, eventMode);
318             }
319 
320             if(obj){
321                 if(!global) return obj;
322                 else if(obj.length) result = result.concat(obj);
323             }else if(child.hitTestPoint(x, y, usePolyCollision)){
324                 if(!global) return child;
325                 else result.push(child);
326             }
327         }
328 
329         return global && result.length ? result : null;
330     },
331 
332     /**
333      * Rewrite render method.
334      * @private
335      */
336     render: function(renderer, delta){
337         Container.superclass.render.call(this, renderer, delta);
338 
339         var children = this.children.slice(0), i, len, child;
340         for(i = 0, len = children.length; i < len; i++){
341             child = children[i];
342             //NOTE: the child could remove or change it's parent
343             if(child.parent === this) child._render(renderer, delta);
344         }
345     }
346 
347 });
348