eliace/ergo-js

View on GitHub
js/core/widget.js

Summary

Maintainability
F
4 days
Test Coverage

//= require data
//= require states
//= require layout
//= require widget-opts
//= require widget-props
//= require widget-list
//= require widget-data
//= require widget-render
//= require widget-jquery





/**
 * Базовый объект для всех виджетов
 *
 * Опции:
 *
 *
 *
 * @class
 * @name Ergo.core.Widget
 * @extends Ergo.core.Object
 * @param {Object} o параметры
 * @param {string} o.layout компоновка дочерних виджетов
 * @param {function} o.layoutFactory фабрика компоновок
 * @param {string} o.as css-классы или состояния (аналог cls и state)
 * @param {string} o.cls css-классы
 * @param {object} o.style хэш стилей
 * @param {string} o.tag html-тег виджета
 * @param {object} o.components хэш компонентов
 * @param {object} o.defaultComponent опции компонента по умолчанию
 * @param {function} o.componentFactory фабрика компонентов
 * @param {array} o.items массив элементов
 * @param {object} o.defaultItem опции элемента по умолчанию
 * @param {function} o.itemFactory фабрика элементов
 * @param {boolean} o.dynamic флаг динамического связывания
 * @param {string} o.dataId ключ в источнике данных
 * @param {object} o.events хэш событий
 * @param {object} o.states хэш состояний
 * @param {object} o.transitions хэш переходов между состояниями
 * @param {string} o.stt предустановленное состояние
 * @param {function} o.rendering обработчик перерисовки
 * @param {string} o.renderTo селектор родительского элемента
 * @param {boolean} o.showOnRender вызов метода show при создании
 * @param {boolean} o.hideOnDestroy вызов метода hide при удалении
 * @param {string|function} o.binding обработчик связывания данных с виджетом
 * @param {object} o.wrapper опции "обертки"
 * @param {function} o.format форматирование "сырого" значения к строке, отображаемой виджетом
 * @param {function} o.unformat преобразование отображаемго/редактируемого значения в "сырые" данные
 * @param {boolean} o.autoBind автоматическое связывание с данными
 * @param {boolean} o.autoRender авто-отрисовка при создании
 * @param {boolean} o.autoHeight автоматический расчет высоты
 * @param {boolean} o.autoWidth автоматический расчет ширины
 *
 * @mixes observable
 * @mixes statable
 *
 * @property {object} options Опции
 * @property {Ergo.core.DOM} dom Узел виртуального DOM
 * @property {jQuery} el
 * @property {Ergo.core.DataSource} data Связанный источник данных
 * @property {Ergo.core.Observer} events Обработчик событий
 * @property {Ergo.core.StateManager} states Обработчик состояний
 * @property {Ergo.core.Items} items Элементы
 * @property {Ergo.core.Components} components Компоненты
 * @property {any} name Служебное имя/индекс/ключ
 * @property {any} value Значение
 * @property {Ergo.core.Widget} parent Родительский виджет
 *
 *
 */
Ergo.defineClass('Ergo.core.Widget', /** @lends Ergo.core.Widget.prototype */{

    extends: 'Ergo.core.Object',

    mixins: ['observable', 'statable'],

    defaults: {
    },



    /**
     * Уничтожение виджета
     *
     * Удаляются связи в виртуальном дереве виджетов, отключается связь с данными,
     * удаляется элемент из DOM-дерева, уничтожаются все дочерние виджеты.
     *
     * @protected
     */
    _destroy: function(root) {
        Ergo.core.Widget.superclass._destroy.call(this);

        var self = this;


        this.events.fire('beforeDestroy');

        // if(this.options.hideOnDestroy && !noHide) {
            // return this.hide().then(function() { self._destroy(true); });
        // }
        // else {

        var destroy = function(){

            if(this.data)
                this.data.events.off(this);

            // очищаем регистрацию обработчиков событий
            if(this.context) {
                this.context.events.off(this);
            }

            if(this.scope) {
                this.scope.events.off(this); //FIXME события могут быть прикреплены не только к скоупуs
            }

            // очищаем регистрацию обработчиков событий
            this.events.off();

            if(this.__c) {

                // удаление в обратном порядке быстрее
                for(var i = this.__c.src.length-1; i >=0 ; i--) {
                    this.__c.src[i]._destroy(false);
                }

            }

//            if(!eventsOnly) {

            // удаляем элемент и все его содержимое (data + event handlers) из документа
            if(this.parent && root !== false)
                this.parent.children.remove(this);

            if(this.__c) {

                this.children.removeAll();

                // очищаем компоновку
//                this.layout.clear();  //?
            }

            if(this.__dom) {
//                this.__dom.clear();
                this.__dom._destroy();
//                $ergo.dom.remove(this.el);
//                this.el.remove();
            }

//            }


            // вызываем метод _destroy для всех дочерних компонентов
            // while( !this.children.isEmpty() ) {
            //     this.children.first()._destroy(true);
            // }

//            this.children.applyAll('_destroy');

            this._destroyed = true;

        };

        if( root !== false ) {
            this.unrender()
            destroy.call(this);
            //            this.unrender().then( destroy.bind(this) );
        }
        else {
            destroy.call(this);
        }

//        }
//        if(this.options.debug)    console.log('_destroyed');

    },




    /**
     * В фазе преконструирования происходит следующее:
     *
     * Преобразуется "сахарное" определение компонентов с префиксом $
     *
     * @protected
     */
    _preConstruct: function(o) {
        Ergo.core.Widget.superclass._preConstruct.call(this, o);
//        this._super(o);



        var self = this;


        // "сахарное" определение компонентов
        for(var i in o) {
            if(i[0] == '$') {
                var key_a = i.split('__');
                var overrides = {};
                var val = overrides;

                while(key_a.length > 0) {
                    var k = key_a.shift();
                    var v = (key_a.length == 0) ? o[i] : {};
                    if(k[0] == '$') {
                        k = k.substr(1);
                        val.components = {};
                        val = val.components[k] = v;
                    }
                    else {
                        val = val[k] = v;
                    }
                }

                $ergo.mergeOptions(o, overrides);
            }
        }








    },





    /**
     * В фазе конструирования виджета:
     * 1. создаются поля
     * 2. создается элемнт DOM-дерева (обернутый jQuery)
     * 3. создаются компоненты
     * 4. создаются элементы
     * 5. регистрируются события (виджета, jquery, ctx)
     * 6. регистрируются обработчики onClick, onDoubleClick
     *
     * @protected
     */
    _construct: function(o) {
        Ergo.core.Widget.superclass._construct.call(this, o);


        var self = this;
//        var el = this.el;






/*
        if(o.rendering) {

            o.rendering.call(this, o);

        }
        else {

            this.el = $(o.html);//this.$html());

            if(!o.html) {
                this.el = $(document.createTextNode(''));
            }

            if('style' in o) this.el.css(o.style);
            if('cls' in o) this.el.addClass($.isString(o.cls) ? o.cls : o.cls.join(' '));
            if('baseCls' in o) this.el.addClass(o.baseCls);

            if(o.as) {
                var as = o.as.join(' ').split(' ');
                var cls = [];
                for(var i = 0; i < as.length; i++) {
                    if(as[i][0] != '+' && as[i][0] != '-')
                        cls.push(as[i]);
                }
                this.el.addClass(cls.join(' '));
            }

        }

        this.el[0]._ergo = this;


      if(o.layout)
             this.layout; //FIXME если компоновка указана явно, то создаем ее сразу
        //  if(o.layout) {
        //      this.layout = (o.layoutFactory || this.layoutFactory)(o.layout);
        //  }
        //  else {
        //      this.layout = new Ergo.core.Layout();
        //  }
        //
        // this.layout.attach(this);//this.layout.options._widget || this);
*/

        var _widget = this;

//        if(o.layout) {
//        this.layout = o.layout;
//        }







        // if(o.tag)
        //     this.layout;

        // if('layout' in o) {
        //     this.layout;
        // }

        //FIXME это нужно перенести в dom


        if('components' in o) {
            var arr = [];
            // преобразуем набор компонентов в массив
            Ergo.each(o.components, function(c, i){
                if(!c.ignore)
                    self.children.add(c, i, 'component');
            } );

        }



        if('items' in o){
            for(var i = 0; i < o.items.length; i++)
                this.children.add(o.items[i]);

        }




        this._bindEvents();

        this._bindStates();




        if('style' in o) {
            this.dom.setStyle(o.style);
        }

        if('cls' in o) {
            this.dom.setClass(o.cls);
        }

        if('css' in o) {
            this.dom.setStyle(o.css);
        }

        if('stt' in o) {
            var stt = o.stt;
            if(Array.isArray(stt)) {
                stt = stt.join(' ');
            }
            stt.split(' ').forEach(function(s) {
                self.states.set(s);
            });
        }






    },




    /**
     * Фаза постконструирования виджета:
     * 1. рендеринг (renderTo)
     * 2. подключение данных (data)
     * 3. установка состояний (state)
     *
     * @protected
     */
    _postConstruct: function(o) {
//        this._super(o);
        Ergo.core.Widget.superclass._postConstruct.call(this, o);




        if('as' in o) {
      var as = o.as.join(' ').split(' ');

            for(var i = 0; i < as.length; i++) {
                if(as[i][0] == '+' || as[i][0] == '&')
                    this.states.set(as[i].substr(1));
                else if(as[i][0] == '-' || as[i][0] == '!')
                    this.states.unset(as[i].substr(1));
                else {
//                    this.dom.el.classList.add(as[i]);
                    this.dom.addClass(as[i]);
                }
            }

      // for(var i = 0; i < clsList.length; i++) {
      //   this.dom.addClass(clsList[i]);
      // }
    }


//        console.log(this.events.events);

        if(Ergo._scope) {
            this.scope = Ergo._scope;
        }


        if('renderTo' in o) {
            this.render(o.renderTo);
        }


        if('data' in o) {
            this.data = o.data;
        }




        if(this.__evt) {

            if(this.events.events['click']) {
                this.dom.events.on('click', function(e) { this.events.fire('click', {}, e); });
            }

            if(this.events.events['doubleClick']) {
                this.dom.events.on('dblclick', function(e) { this.events.fire('doubleClick', {}, e); });
            }

            this.events.fire('afterConstruct');
        }


        // this.events.rise = Ergo.rise;
        // this.events.sink = Ergo.sink;

    },










    // factory: function(item) {
        // if(!(item instanceof Ergo.core.Widget)) {
            // item = this.options.componentFactory.call(this, item);
        // }
        // return item;
    // },




    /**
     * Генерация всплывающего события
     *
     * Если имя события не определено, то используется имя виджета ( `w.opt('name')` )
     *
     * Если у оригинального события определен метод `stop()`, то он вызывается
     *
     * @param {Object} originalEvent Оригинальное событие
     * @param {string} eventContext контекст события (`jquery`, `scope` и т.д.)
     * @param {string} name Имя события
     *
     * @deprecated
     */
    action: function(v, event, eventType) {

        v = v || this.opt('name');
        // if(arguments.length == 1) {
        //     e = v;
        //     v = this.opt('name');
        // }

        // if( v == null /*|| $.isNumeric(v)*/ )
        //     throw new Error('Invalid action name ['+v+"]");

        this.rise(''+v, {}, event);

        if(eventType != v && event.stop)
            event.stop();
    },














//     filter: function(type, criteria) {
//
//         if(type == 'render') {
// //            if(criteria)
//             this.options.renderFilter = criteria;
//             this._rerender();
//         }
//
//         if(type == 'compose') {
//             if(this.options.dynamic) {
// //                if(criteria)
//                 this.options.dynamicFilter = criteria;
//                 this._rebind();
//             }
//         }
//
//     },




    // filter: function(criteria, opt, type) {

    //     type = type || 'items';

    //     this[type].each(function(item) {
    //         var ok = criteria( opt ? item.opt(opt) : item );
    //         if( !ok ) {
    //             item.unrender();
    //         }
    //         else if(!item._rendered) {
    //             item.render();
    //         }
    //     });

    // },












    /**
     * Рекурсивный обход всех дочерних виджетов
     *
     * @param {Object} callback метод, вызываемый для каждого компонента
     */
    walk: function(callback) {
        if( callback.call(this, this) === false)
            return;

        if(!this.__c)
            return;

        this.children.each(function(item){
            item.walk(callback);
        });
    },




    /**
     * Получение следующего соседнего элемента, если виджет является элементом коллекции
     */
    next: function() {
        if(!this.parent) return null;
        return this.parent.item(this._index+1);
    },

    /**
     * Получение предыдущего соседнего элемента, если виджет является элементом коллекции
     */
    prev: function() {
        if(!this.parent) return null;
        return this.parent.item(this._index-1);
    },

    /**
     * Поиск элемента
     *
     * @param {int|string|class|Function|Object} i индекс|имя|класс|фильтр|шаблон
     * @returns {Ergo.core.Widget}
     *
     * @example
     * w.item(2) - находит элемент с индексом 2
     * w.item('header') - находит элемент с именем "header"
     * w.item(Ergo.widgets.MyWidget) - элемент класса Ergo.widgets.MyWidget
     * w.item({tag: 'hello'}) - элемент, у которого свойство "tag" равно "hello"
     *
     */
    item: function(i) {
//        return this.children.find(Ergo.filters.by_widget(i));
        var finder = Ergo.by_widget(i);
        var j = 0;
        return this.children.find(function(item, i){
            if(item._type == 'item'){
                if(finder.call(this, item, j)) return true;
                j++;
            }
//            return item.type == 'item' && ;
        });
    },


    /**
     * Поиск компонента
     *
     * Поиск элемента
     *
     * @param {string|class|Function|Object} i имя|класс|фильтр|шаблон
     * @returns {Ergo.core.Widget}
     *
     */
    component: function(i) {
        var finder = Ergo.by_widget(i);
        return this.children.find(function(item, j){
            return item._type == 'component' && finder.call(this, item, j);
        });
    },





    //-------------------------------------------
    // Методы для работы с компонентами виджета
    //-------------------------------------------


    /**
     * Получение списка всех родителей виджета.
     *
     * Еще здесь применим термин "путь" для деревьев.
     *
     * @returns {Array} список родительских виджетов
     */
    parents: function(list) {
        if(arguments.length == 0) list = [];
        if(!this.parent) return list;
        list.push(this.parent);
        return this.parent.parents(list);
    },







    //---------------------------------------------
    // Методы работы с подсоединенными данными
    //---------------------------------------------



    get children() {
        if(!this.__c) {
            this.__c = new Ergo.core.Children(this);
        }
        return this.__c;
    },

    get components() {
        if(!this.__cmp) {
            this.__cmp = new Ergo.core.Components(this, {type: 'component'});
        }
        return this.__cmp;
    },

    get items() {
        if(!this.__itm) {
            this.__itm = new Ergo.core.Items(this, {type: 'item'});
        }
        return this.__itm;
    },

    get data() {
        return this.__dta;
    },

    set data(v) {
        if(v != null) {
            this.bind(v);
        }
        else {
//            this._unbindEvents('data');
            this.unbind();
        }
    },


    get dom() {
        if(!this.__dom) {
            this._bindDOM();
            this._bindEvents('dom');
        }
        return this.__dom;
    },


    get scope() {
        if(!this.__scp && this.parent) {
            this.__scp = this.parent.scope;
        }
        return this.__scp;
    },

    set scope(v) {
        if(v != null) {
            this.__scp = v;
            this._bindEvents('scope');
        }
        else if(this.__scp) {
            this.__scp.off(this);
            delete this.__scp;
        }

    },

    get context() {
//        return $context || Ergo.context;
        //TODO возможен случай когда контекст отличается
        if(!this.__ctx) {
            this.__ctx = $context || Ergo.context;
//            this.__ctx = (this.scope) ? this.scope._context : Ergo.context;
            this._bindEvents('context');
        }
        return this.__ctx;
    },


    // get dom() {
    //     var el = this.el;
    //     if(!this.__dom) {
    //         this.__dom = {
    //             events: {
    //                 on: function(name, callback) {
    //                     el.addEventListener(name, callback);
    //                 }
    //             }
    //         }
    //     }
    //     return this.__dom;
    // },


    // get layout() {
    //     if(!this.__l) {
    //         var o = this.options;
    //         if(o.layout) {
 //              this.__l = (o.layoutFactory || this.layoutFactory)(o.layout);
    //         }
    //         else {
    //          this.__l = new Ergo.core.Layout();
    //         }
    //
 //             this.__l.attach(this);//this.layout.options._widget || this);
    //
    //         if( o.dynamic ) {
    //             this.events.on('diff', function(e) {
    //                 // перестраиваем компоновку
    //                 this._rerender(false, {created: e.created, deleted: e.deleted, updated: e.updated});
    //             }.bind(this));
    //         }
    //
    //     }
    //     return this.__l;
    // },


    get name() {
        return this._name || this._key || this._index;
    },

    set name(v) {
        this._name = v;
    },



    // set format(v) {
    //     this.__fmt = (typeof v == 'string') ? $ergo.formatObj.curry(v) : v;
    // },
    //
    // get format() {
    //     return this.__fmt;
    // },
    //
    // set unformat(v) {
    //     this.__ufmt = (typeof v == 'string') ? $ergo.unformatObj.curry(v) : v;
    // },
    //
    // get unformat() {
    //     return this.__ufmt;
    // },

    get value() {
        var val;
        var o = this.options;

        if(this.data)
            val = this.data.get();
//        else if('__val' in this)
        else
            val = this.__val;
        // else
        //     val = this.opt('text');

//            val = (o.value) ? o.value.call(this) : this.opt('text');

        // если присутствует функция форматирования, то используем ее
        var fmt = this._format || this.options.format;
        if(fmt) {
            if(typeof fmt == 'string') {
                var m = fmt.match(/^#{([^}{]+?)}$/);
                if( m ) {
                    val = $ergo.formatValue.call(this, m[1], val);
                }
                else {
                    val = $ergo.formatObj.call(this, fmt, val);
                }
            }
            else {
                val = fmt.call(this, val);
            }
//            val = (typeof fmt == 'string') ? $ergo.formatObj.call(this, fmt, val) : fmt.call(this, val);// this.__fmt.call(this, val);
        }

        return val;
    },




    set value(val) {

//        if(this._lock_value_change) return;

        var o = this.options;

        // deprecated
//        if(o.store)
//            val = o.store.call(this, val);

        var ufmt = this._unformat || this.options.unformat;
        if(ufmt) {
            val = (typeof ufmt == 'string') ? $ergo.unformatObj.call(this, ufmt, val) : ufmt.call(this, val);//this.__ufmt.call(this, val);
        }

        var fmt = this._format || this.options.format;
        if( (fmt && !ufmt) || (!fmt && ufmt) ) {
            console.warn('No matching format and unformat');
        }



        if(this.data) {

            // связывание будет обновлено автоматически
            this.data.set(val);

        }
        else {
            this.__val = val;
            // а здесь связывание нужно обновить
            this._dataChanged();
//            o.value ? o.value.call(this, val) : this.opt('text', val);
        }


//        if(o.binding)
//            o.binding.call(this, val);

//        this.events.fire('valueChanged', {'value': val});

    },


/*
    format: function(val) {
        // если присутствует функция форматирования, то используем ее
        if(this.options.format)
            val = this.options.format.call(this, val);
        return val;
    },
*/



    // $valueChanged: function() {
//
    // },






    // $unknown_opt: function(i, v) {
//
        // // проверяем состояния
        // if(i in this.states._states) {
            // this.states.toggle(i, v)
        // }
        // // проверяем группы
        // else if(i in this.states._exclusions) {
            // this.states.set(v);
        // }
//
    // }





    prop: function(i, v, defaultValue) {

        if(arguments.length == 1 || arguments.length == 3) {

            if( this.options.get && (i in this.options.get) ) {
                return this.options.get[i].bind(this)(i);
            }
            else if( (i in this.props) && this.props[i].get ) {
                return this.props[i].get.bind(this)(i);
            }
            else if( i in this.props.get ) {
                return this.props.get[i].bind(this)(i);
            }
            else if( (i in this) && $ergo.hasGetter(this, i) ) {
                return this[i];
            }
            else if((this.__stt) && (i in this.__stt._states)) {
                return this.states.is(i);
            }

            return defaultValue;
        }
        else if(arguments.length == 2) {

            if( this.options.set && (i in this.options.set) ) {
                this.options.set[i].bind(this)(v, i);
            }
            else if( (i in this.props) && this.props[i].set ) {
                this.props[i].set.bind(this)(v, i);
            }
            else if( (i in this.props.set) ) {
                this.props.set[i].bind(this)(v, i);
            }
            else if( (i in this) && $ergo.hasSetter(this, i) ) {
                this[i] = v;
            }
            else if(i in this.states._states) {
                this.states.toggle(i, v);
            }
            //FIXME
            else if(i in this.states._groups) {
                this.states.set(v);
            }
            // else {
            //     console.warn('Property ['+i+'] not found');
            // }

            return this;
        }

    },






    //  _opt: function(o) {
    //
 //         for(var i in o) {
 //             if( !(i in $ergo.rules) ) {
 //                 if( (i in this) && $ergo.setter(this, i) ) {
 //                     this[i] = o[i];
 //                 }
    //             else if(i in this.states._states) {
    //                 this.states.toggle(i, o[i]);
    //             }
    //             else if(i in this.states._exclusives) {
    //                 this.states.set(o[i]);
    //             }
 //             }
 //         }
    //
 //     },



/*
    _opt: function(o) {

//        var self = this;

        var exclusions = {
            components: true,
            items: true,
            set: true,
            get: true,
            mixins: true,
            defaultItem: true,
            defaultComponent: true,
            nestedItem: true,
            events: true,
            states: true,
            attributes: true,
        };

//        var el = this.el;

        for(var i in o) {

            if( (i in exclusions) || i[0] == '$') continue;

            // проверяем наличие сеттеров опций
            if(this.options.set && this.options.set[i])
                this.options.set[i].call(this, o[i], this.options);
            // если сеттер опций не найден, проверяем наличие java-like сеттера
            else {
                // проверяем наличие Java-like сеттеров
                var java_setter = 'set_'+i;//.capitalize();
                if(this[java_setter])
                    this[java_setter](o[i]);
                // // проверяем состояния
                // else if(this.layout._etype == 'layouts:'+i)
                //     this.layout
                // проверяем состояния
                else if(i in this.states._states)
                    this.states.toggle(i, o[i]);
                // проверяем группы состояний
                else if( (i in this) && Ergo.setter(this, i) ) {
                    // var desc = Object.getOwnPropertyDescriptor(this, i);
                    // if(desc && desc.set)
                    this[i] = o[i];
                }
                else if(i in this.states._exclusives)
                    this.states.set(o[i]);
                // проверяем атрибуты
                else if(this.attributes.indexOf(i) != -1)
                    o[i] ? this.el.attr(i, o[i]) : this.el.removeAttr(i);

            }
        }


//        profiler.tick('opt', 'other');
//
//        profiler.stop('opt');

    },
*/


    /**
     *
     */
    layoutFactory: function(layout) {
        if( $.isString(layout) )
            layout = $.ergo({etype: layout}, 'layouts');
        else if(!(layout instanceof Ergo.core.Layout))
            layout = $.ergo(Ergo.merge({etype: 'default'}, layout), 'layouts');
        return layout;
    },




    /**
     * "Всплытие" события по дереву виджетов
     *
     * @see {@link Ergo.core.Observer#fire}
     */
    rise: function(name, e, baseEvent) {
        if(e == null) {
            e = {};
        }
        else if( e instanceof Ergo.core.Event || e.constructor == Object ) {
            //
        }
        else {
            e = {$data: e}
        }
//        if(!e) e = {};
        e.target = e.target || this;
        e = this.events.fire(name, e, baseEvent);
        if(this.parent && !e.stopped) {
            this.parent.rise(name, e);
        }
    },


    /**
     * "Погружение" события по дереву виджетов
     *
     * @see {@link Ergo.core.Observer#fire}
     */
    sink: function(name, e, baseEvent) {
        if(!e) e = {};
        e.target = e.target || this;
        e = this.events.fire(name, e, baseEvent);
        if(!e.stopped) {
            this.children.each(function(child){
                child.sink(name, e);
            });
        }
    },




    /**
     * Обработчик "потерянного" состояния
     *
     * Устанавливает класс `dom`
     */
    _missedState: function(name, on, data) {
        on ? this.dom.addClass(name) : this.dom.removeClass(name);
    },



    // _missedAction: function(name, on, data) {
    //     on ? this.dom.addClass(name) : this.dom.removeClass(name);
    // }








/*
    get __skeleton() {

        var s = {
            components: [],
            items: [],
            states: [],
            events: [],
            options: {}
        };



        var exclusions = {
            components: true,
            items: true,
            set: true,
            get: true,
            mixins: true,
            defaultItem: true,
            defaultComponent: true,
            nestedItem: true,
            events: true,
            states: true,
            attributes: true,
            __skeleton: true,
            include: true,
            children: true
        };

        // simple options
        for(var i in this.options) {
            if( !(i in exclusions) && !/on\S/.test(i) ) {
                s.options[i] = ['get', 'set'];
            }
        }

        // get/set options
        if( this.options.get ) {
            for(var i in get) {
                if( !(i in s.options) ) {
                    s.options[i] = ['get'];
                }
            }
        }
        if( this.options.set ) {
            for(var i in set) {
                if( !(i in s.options) ) {
                    s.options[i].push('set');
                }
            }
        }

        // state options
        for(var i in this.states._states) {
            s.options[i] = ['get', 'set'];
        }
        for(var i in this.states._exclusives) {
            s.options[i] = ['get', 'set'];
        }

        // property options
        var w = this;
        while(w) {
            for(var i in w) {
                var desc = Object.getOwnPropertyDescriptor(w, i);
                if(desc && !(i in exclusions)) {
                    if(desc.get) {
                        s.options[i] = s.options[i] ? s.options[i].concat('get') : ['get'];
                    }
                    if(desc.set) {
                        s.options[i] = s.options[i] ? s.options[i].concat('set') : ['set'];
                    }
                }
            }
            w = w.constructor.superclass;
        }


        this.components.each(function(c, i) {
            s.components.push(i);
        })


        s.states = Object.keys(this.states._states);

        s.events = Object.keys(this.events.events);

        return s;
    }
*/




    // setTextSelection: function(v) {
        // if(!v) {
      // if($.browser.mozilla){//Firefox
        // this.el.css('MozUserSelect','none');
      // }
      // else if($.browser.msie){//IE
        // this.el.bind('selectstart',function(){return false;});
      // }
      // else{//Opera, etc.
        // this.el.mousedown(function(){return false;});
      // }
        // }
    // }





}, 'widgets:widget');




//Ergo.merge(Ergo.core.Widget.prototype, Ergo.alias('mixins:observable'));

//Ergo.merge(Ergo.core.Widget.prototype, Ergo.alias('mixins:statable'));

//Ergo.merge(Ergo.core.Widget.prototype, Ergo.WidgetOptions);

Ergo.deepMerge(Ergo.core.Widget.prototype, Ergo.WidgetProps);

Ergo.merge(Ergo.core.Widget.prototype, Ergo.WidgetData);

Ergo.merge(Ergo.core.Widget.prototype, Ergo.WidgetRender);

Ergo.merge(Ergo.core.Widget.prototype, Ergo.alias('mixins:jquery'));

/*
Ergo.merge(Ergo.core.Widget.prototype, Ergo.WidgetAttributes);

Ergo.merge(Ergo.core.Widget.prototype, Ergo.WidgetRender);
*/


//Ergo.widget = function(){
//    if(arguments.length == 1) return Ergo.object(arguments[0]);
//    return Ergo.object( Ergo.smart_override.apply(this, arguments) );
//};

Ergo.$widget = Ergo.object;

// Ergo.$widget = function(o, etype) {
    // return Ergo.object(o, 'widget:'+etype);
// };


// Ergo.after_rise = function(e, type, base) {
//     if(this.parent && !e.stopped) this.parent.events.fire(type, e, base);
// };
//
//
// Ergo.after_sink = function(e, type, base) {
//     this.children.each(function(child){
//         child.events.fire(type, e, base);
//     });
// };
//
//
// Ergo.rise = function(name, e, baseEvent) {
//     if(!e) e = {};//new Ergo.core.Event();
//     e.after = Ergo.after_rise;
//     e.target = e.target || this.target;
//     this.fire(name, e, baseEvent);
// };
//
// Ergo.sink = function(name, e, baseEvent) {
//     if(!e) e = {};//new Ergo.core.Event();
//     e.after = Ergo.after_sink;
//     e.target = e.target || this.target;
//     this.fire(name, e, baseEvent);
// };





//------------------------------
// Интегрируемся в jQuery
//------------------------------


$.ergo = $ergo;



$.fn.ergo = function(o) {
    if(this.length > 0){
        var widget = (this[0]._dom) ? this[0]._dom._widget : undefined;// this.data('ergo-widget');
        if(widget) return widget;
        if(!o) return undefined;
        o.html = this;
    }
    else if(arguments.length == 0) return null;

    return $.ergo(o);
};









/**
 * Пространство для виджетов
 *
 * @namespace
 */
Ergo.widgets = {};


Ergo.$widgets = $ergo.object.bind(this, 'widgets');