eliace/ergo-js

View on GitHub
js/core/widget-list.js

Summary

Maintainability
F
5 days
Test Coverage

//= require array



/**
 * Массив виджетов
 *
 *
 * @class
 * @name Ergo.core.Children
 * @extends Ergo.core.Array
 *
 *
 */
Ergo.defineClass('Ergo.core.Children', /** @lends Ergo.core.Children.prototype */{

    extends: 'Ergo.core.Array',

//     defaults: {
// //        plugins: [Ergo.Observable]
// //        include: 'observable'
//     },


    _initialize: function(w) {
//        this._super(null, o);

//        Ergo.core.WidgetArray.superclass._initialize.call(this, null, o);

//        this.options = o || {};
        this.src = [];
//        this.events = new Ergo.events.Observer(this);

        this.autobinding = true;

        this._widget = w;
    },


    factory: function(o, type) {

        var default_opt = this.options.defaultOpt || 'text';
        if($.isString(o)) {
            var v = o;
            o = (this.options.shortcuts || {})[v];
            if(!o) {
                o = {};
                o[default_opt] = v;//{text: o};
            }
        }
        else if(Array.isArray(o)) o = {items: o};
        var default_child = 'default' + type[0].toUpperCase() + type.substr(1);
        default_child = this.options[default_child];

//        for(var i = 0; i < default_child.length; i++) {
            if( $.isString(default_child) ) {
                default_child = {etype: default_child};
            }
//            default_child[i] = $ergo.copy(default_child[i]);
//        }

//        console.log(type, default_child, o, this.options);

        return $.ergo( [default_child, o] );//, null, this.scope );
//        return $.ergo( Ergo.smart_override({}, this.options[default_child], o) );
    },



    // get _source() {
    //     return this.src;
    // },




    add: function(item, i, type) {

//        console.log('add item');

//        var key;
        var w = this._widget;

//        item = w.factory(item);

        // если не определен тип компонента
        if(type == undefined) {
            // если не определен ключ компонента
            type = (i && $.isString(i)) ? 'component' : 'item';
        }

//        type = type || 'item';

        // создаем виджет с помощью фабрики элементов
        if(!(item instanceof Ergo.core.Widget)) {
            item = (w.options[type+'Factory'] || this.factory).call(w, item, type);
        }
//        item = (w.options[type+'Factory'] || this.factory).call(w, item, type);

        item._type = type;

        // для элементов с текстовыми ключами (компонентов) сохраняем ключ в поле _key
        if(i && (typeof i === 'string')) {
            item._key = i;
            i = undefined;
        }
        else {
            item._index = i;
        }

        // определяем поле parent
        item.parent = w;


        // определяем индекс элемента в children
        if(i != null && ('_index' in item)) {//item._index) {
            // FIXME здесь нужно немного эвристики
            // if(this.src[this.src.length-1]._index < item._index)
                // i =
            for(i = this.src.length-1; i >= 0; i--) {
                if(('_index' in this.src[i]) && this.src[i]._index < item._index) {
//                    i--;
//                    i++;
                    break;
                }
            }

            i++;
        }
        else if(i == null && ('_index' in item)) {
            // FIXME последний индекс лучше сохранять после пересчета
            item._index = 0;
            for(var j = this.src.length-1; j >= 0; j--) {
                if(this.src[j]._type == item._type) {
                    item._index = this.src[j]._index + 1;
//                    item.dom.el._index = item._index; //WARN это действие должно осуществляться в layout
                    break;
                }
            }
        }

//        var i0 = i;


//        console.log('i', i);
        // добавляем элемент в коллекцию
//        i = this._super(item, i);
        i = Ergo.core.Children.superclass.add.call(this, item, i);



//        console.log(i0 + ' > '+i);

        // обновляем свойство _index у соседних элементов
        for(var j = i+1; j < this.src.length; j++) {
            if('_index' in this.src[j]) {
                this.src[j]._index++;
//                this.src[j].dom.el._index++; //WARN это действие должно осуществляться в layout
            }
        }





//            this.src[j]._index = j;

        //FIXME скорее всего вызов метода show должен находиться не здесь
        // if(item.options.autoRender === true) {
            // if(item.options.showOnRender) item.show();
            // if(item.options.hideOnRender) item.hide();
        // }

        // для элементов с текстовыми ключами (компонентов) добавляем accessor
        if(item._key) {
//            w[item._key] = item;
            w['$'+item._key] = item;
        }



        // добавляем элемент в компоновку с индексом i (для компонентов он равен undefined)
        // if(item.options.autoRender === true)
        //     w.dom.add(item, item._index);//i);


        // выполняем иерархическое связывание данных (автобиндинг)
        if(w.data && !item.data && this.autobinding)
            item.bind(w.data, false, false);



//        console.log('item:add');
        //TODO здесь бы применить метод вызова опций как для компоновки
        this._widget.events.fire('item#added', {'item': item});

        return item;
    },




    removeAt: function(i) {

//        var key;
        var w = this._widget;

        // // для компонентов определяем индекс через accessor
        // if($.isString(i)) {
// //            key = i;
            // i = w[i]._index;
        // }


//        var item = this._super(i);
        var item = Ergo.core.Children.superclass.removeAt.call(this, i);


//        if('hide' in item) item.hide();

        // обновляем свойство _index у соседних элементов
        for(var j = i; j < this.src.length; j++) {
            if('_index' in this.src[j]) {
                this.src[j]._index--;
//                this.src[j].dom.el._index--;
            }
        }

//            this.src[j]._index = j;

        // поля parent, _index и _key больше не нужны
        delete item.parent;
        delete item._index;

        if(item._key) {
            delete w['$'+item._key];
//            delete w[item._key];
            delete item._key;
        }


        // if(item.options.hideOnDestroy) {
            // item.hide().then(function(){ w.layout.remove(item); });
        // }
        // else {
            // удаляем элемент из компоновки
//        w.layout.remove(item); //FIXME
        // }


//        this.events.fire('item:remove', {'item': item});

        return item;
    },




    each: function(callback/* filter, sorter*/) {

        // var c = this._widget; // возможно не лучшее решение, но практичное
        //
        // var values = this.src;
        //
        // var filter = filter || c.options.filter;
        // var sorter = sorter || c.options.sorter;
        //
        // if(filter || sorter) {
        //
        //     var kv_a = [];
        //
        //     // Filtering source and mapping it to KV-array
        //     values.forEach(function(v, i) {
        //         if(!filter || filter(v, i)) {
        //             kv_a.push( [i, v] );
        //         }
        //     });
        //
        //
        //     if(sorter) {
        //         // Sorting KV-array
        //         kv_a.sort( sorter );
        //     }
        //
        //
        //     for(var i = 0; i < kv_a.length; i++) {
        //         var kv = kv_a[i];
        //         var prev = i ? kv_a[i-1][1] : undefined;
        //         callback.call(c, kv[1], i, prev);//kv[0]);
        //     }
        //
        // }
        // else {
            // Basic each
            this.src.forEach(callback);
//            Ergo.each(this.src, callback);

//        }

    },



    stream: function(filter, sorter, pager, callback) {

        var c = this._widget; // возможно не лучшее решение, но практичное

        var filter = filter || c.options.filter;
        var sorter = sorter || c.options.sorter;
        var pager = pager || c.options.pager;

        if(filter || sorter || pager) {

            var kv_a = [];

            // Filtering source and mapping it to KV-array
            this.src.forEach(function(v, i) {
                if(!filter || filter(v, i)) {
                    kv_a.push( [i, v] );
                }
            });


            if(sorter) {
                // Sorting KV-array
                kv_a.sort( sorter );
            }


            for(var i = 0; i < kv_a.length; i++) {
                var kv = kv_a[i];
//                var prev = (i > 0) ? kv_a[i-1][1] : undefined;
                callback.call(c, kv[1], i);//kv[0]);
            }

            //TODO pager
        }
        else{
            this.src.forEach(callback.bind(c));
        }

    },







    removeAll: function() {

        var w = this._widget;

        for(var i = 0; i < this.src.length; i++) {
            var item = this.src[i];

            delete item.parent;
            delete item._index;

            if(item._key) {
                delete w['$'+item._key];
                delete w[item._key];
                delete item._key;
            }

        }

        this.src.length = 0; // possible bugs
    },




    // _destroy_all: function() {
        // while(this.src.length)
            // this.removeAt(0)._destroy();
    // }

//    find: function(i) {
//        return Ergo.core.ItemCollection.superclass.find.call(this, Ergo.utils.widget_filter(i));
//    }

});








/**
 * Коллекция компонентов виджета
 *
 *
 * @class
 * @name Ergo.core.Components
 * @extends Ergo.core.Array
 *
 *
 */
Ergo.defineClass('Ergo.core.Components', /** @lends Ergo.core.Components.prototype */ {

    extends: 'Ergo.core.Array',

//     defaults: {
// //        plugins: [Ergo.Observable]
// //        include: 'observable'
//     },

    _type: 'component',


    _initialize: function(w) {//}, o) {
//        this._super(null, o);

//        this.options = o;

        this.src = [];
//        this.events = new Ergo.events.Observer(this);

        this._widget = w;
    },


    get _source() {
        var result = {};
        var _type = this._type;
//        var o = this.options;
        this._widget.children.src.forEach(function(c) { if(c._type == _type) result[c._key] = c; });
        return result;
    },



    _factory: function(v) {
        return new Ergo.core.Components(this._widget);
    },


    /**
     * Установка значения
     * @param {Object} i ключ
     * @param {Object} item значение
     */
    set: function(i, item) {
        // if(i in this._widget)
        //     this.removeAt(i);
        if( ('$'+i) in this._widget)
            this._widget['$'+i]._destroy();
//            this._widget.children.removeAt(i);
        return this._widget.children.add(item, i, this._type);
    },




    /**
     * Получение значения по ключу
     * @param {Object} i
     */
    get: function(i) {
        return this._source[i];
    },

    /**
     * Добавление нового значения
     * @param {Object} item значение
     * @param {Object} [i] ключ
     *
     * Аналогично по работе методу set
     */
    // add: function(item, i) {
        // this.src[i] = item;
// //        this.events.fire('item:add', {'item': item});
    // },



    /**
     * Удаление значения по ключу
     * @param {Object} i ключ
     */
    removeAt: function(i) {
//        return this._widget.children.removeAt(i);
        var removed = this._widget.children.removeIf(function(v) {    return v._key == i;    });

        return removed.length == 0 ? null : removed[0];
    },

    /**
     * Удаление значения
     *
     * Для удаления используется метод removeAt
     *
     * @param {Object} item значение
     */
    remove: function(item) {
        return this.removeAt(item._key);
    },

    /**
     * Удаление значения по условию
     *
     * Для удаления используется метод removeAt
     *
     * @param {Object} criteria функция-условие
     *
     * Значение удаляеся, если результат, возвращаемый criteria равен true
     */
    removeIf: function(criteria) {
        var keys = Ergo.filter_keys(this._source, criteria);
        keys.sort(Ergo.sort_numbers).reverse();
        var removed = [];
        for(var i = 0; i < keys.length; i++) removed.push( this.removeAt(keys[i]) );
        return removed;
    },


    removeAll: function() {
        var src = this._source;
        for(i in src)
            this.removeAt(i);
    },


    /**
     * Очистка коллекции от всех значений
     */
    clear: function() {
        this.removeAll();
    },

    /**
     * Последовательный обход всех значений
     * @param {Object} callback
     * @param {Object} delegate
     */
    // each: function(callback) {
    //     return Ergo.each(this._source, callback);
    // },

    each: function(callback/*, filter, sorter*/) {

        // var c = this._widget; // возможно не лучшее решение, но практичное
        //
        // var values = this._source;
        //
        // var filter = filter || c.options.filter;
        // var sorter = sorter || c.options.sorter;
        //
        // if(filter || sorter) {
        //
        //     var kv_a = [];
        //
        //     // Filtering source and mapping it to KV-array
        //     values.forEach(function(v, i) {
        //         if(!filter || filter(v, i)) {
        //             kv_a.push( [i, v] );
        //         }
        //     });
        //
        //
        //     if(sorter) {
        //         // Sorting KV-array
        //         kv_a.sort( sorter );
        //     }
        //
        //
        //     for(var i = 0; i < kv_a.length; i++) {
        //         var kv = kv_a[i];
        //         var prev = (i > 0) ? kv_a[i-1][1] : undefined;
        //         callback.call(c, kv[1], i, prev);//kv[0]);
        //     }
        //
        // }
        // else {
            // Basic each
            this._source.forEach(callback);
//            Ergo.each(this.src, callback);

//        }

    },




    stream: function(filter, sorter, pager, callback) {

        var c = this._widget; // возможно не лучшее решение, но практичное

        var filter = filter || c.options.filter;
        var sorter = sorter || c.options.sorter;
        var pager = pager || c.options.pager;


        if(filter || sorter || pager) {

            var kv_a = [];

            // Filtering source and mapping it to KV-array
            this._source.forEach(function(v, i) {
                if(!filter || filter(v, i)) {
                    kv_a.push( [i, v] );
                }
            });


            if(sorter) {
                // Sorting KV-array
                kv_a.sort( sorter );
            }


            for(var i = 0; i < kv_a.length; i++) {
                var kv = kv_a[i];
//                var prev = (i > 0) ? kv_a[i-1][1] : undefined;
                callback.call(c, kv[1], i);//kv[0]);
            }

            //TODO pager

        }
        else{
            this._source.forEach(callback.bind(c));
        }

    },




//    ensure: function(i) {
//
//    },

    /**
     * Поиск первого элемента, удовлетворяющего критерию
     */
    find: function(criteria) {
        return Ergo.find(this._source, criteria);
    },

    /**
     * Поиск всех элементов, удовлетворяющих критерию
     */
    findAll: function(criteria) {
        return Ergo.filter(this._source, callback);
    },



    //
    //TODO методам filter и map имеет смысл возвращать коллекцию, а не значение
    //

    /**
     * Фильтрация элементов
     */
    filter: function(callback) {
        return this.create( Ergo.filter(this._source, callback) );
    },

    /**
     * Отображение элементов
     */
    map: function(callback) {
        return this.create( Ergo.map(this._source, callback) );
    },

    /**
     * Проверка вхождения значения в коллекцию
     * @param {Object} criteria
     */
    includes: function(criteria) {
        return Ergo.contains(this._source, callback);
    },

    /**
     * Размер коллекции
     */
    count: function() {
        var n = 0;
        var src = this._source;
        for(var i in src) n++;
        return n;
    },

    /**
     * Проверка, является ли коллекция пустой
     */
    isEmpty: function() {
        return this.count() == 0;
    },

    /**
     * Получение ключа элемента
     * @param {Object} item
     */
    keyOf: function(item) {
        return Ergo.keyOf(this._source, item);
    },

    /**
     * Вызов для всех элементов коллекции указанного метода
     *
     * @param {Object} m
     * @param {Object} args
     */
    applyAll: function(m, args) {
        Ergo.applyAll(this._source, m, args);
    },


    /**
     * Проверка наличия элемента с указанным ключом
     * @param {Object} i ключ
     */
    has_key: function(i) {
        return (i in this._source);
    },

    /**
     * Список всех ключей в коллекции
     */
    keys: function() {
        var k = [];
        for(var i in this._source) k.push(i);
        return k;
    },


    add: function(item, i) {
        return this._widget.children.add(item, i, this._type);
    },


    first: function() {
        return this._source[0];
    },


    last: function() {
        return this._source[src.length-1];
    }


});




/**
 * Коллекция элементов виджета
 *
 *
 * @class
 * @name Ergo.core.Items
 * @extends Ergo.core.Components
 *
 *
 */
Ergo.defineClass('Ergo.core.Items', /** @lends Ergo.core.Items.prototype */ {

    extends: 'Ergo.core.Components',

    _type: 'item',

    get _source() {
        var result = [];
        this._widget.children.src.forEach(function(c) { if(!('_key' in c)) result.push(c); });
        return result;
    },



    _factory: function(v) {
        return new Ergo.core.Items(this._widget);
    },


    last: function() {
        var src = this._source;
        return src[src.length-1];
    },

    count: function() {
        var src = this._source;
        return src.length;
    },

    removeAll: function() {
        var src = this._source;
        for(var i = 0; i < src.length; i++)
            this.remove(src[i]);//_at(src[i]._index);
    },



    removeAt: function(i) {
        var removed = this._widget.children.removeIf(function(v) {    return v._index === i;    });

        return removed.length == 0 ? null : removed[0];
    },


    remove: function(item) {
        return this.removeAt(item._index);
    },



/*
    set: function(i, item) {
        this._widget.children.add(item, i, 'item');
    },

    add: function(item, i) {
        this._widget.children.add(item, i, 'item');
    }
*/

});