      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