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