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