/*
 * Create List Filter element
 *
 * Usage:
 *  <$('#input[type=text]').listFilter({Object} {url:someUrl, container:'div#list'})>
 *  The input element used as input field for filtration
 *  The options argument requre "source" or "url" param
 *
 *  <$('#select').listFilter({Object} {container:$('div#list')})>
 *  Create the listfilter from a given select element
 *  The select's options used as a source for filtration, and a source|url params are not required
 *  The select element become replaced with a input[type=text] element for filtration
 *
 */
(function($)
{
    var YF_ListFilter_Interface = function()
    {
        this.data           = null;

        this.process        = function()
        {
            this.ready()
        };

        this.getData        = function()
        {
            return this.data
        };

        this.ready          = function()
        {
            $(this).trigger('onReady')
        };
    };


    var YF_ListFilter_Interface_DataFunction= function(opt)
    {
        YF_ListFilter_Interface.apply(this);

        var _ready          = 0;

        this.dataCaching    = (undefined == opt.dataCaching) ? true : Boolean(opt.dataCaching);

        this.source         = opt.source;

        this.process        = function(opt)
        {
            if (!_ready)
            {
                this.data = this.source(this, opt);
                if (this.dataCaching)
                    _ready = 1
            }
            this.ready()
        };
    };

    var YF_ListFilter_Interface_DataStatic  = function(opt)
    {
        YF_ListFilter_Interface.apply(this);

        var _ready          = 0;

        this.dataCaching    = (undefined == opt.dataCaching) ? true : Boolean(opt.dataCaching);

        this.source         = opt.source;

        this.process        = function()
        {
            if (!_ready)
            {
                var data = [];
                switch (true)
                {
                    case 'string' == typeof this.source:
                        this.source = $(this.source);
                    case this.source instanceof $:
                        this.source.each(function(){
                            ($.nodeName(this, 'select'))
                                && $('option', this).each(function(){
                                    (this.value || this.innerHTML)
                                        && data.push({key:this.value, val:this.innerHTML})
                                })
                                
                        });
                    break;

                    case this.source instanceof Array:
                        data = $.extend([], this.source);
                    break;
                }
                this.data = data;
                if (this.dataCaching)
                    _ready = 1
            }
            this.ready()
        };
    };

    var YF_ListFilter_Interface_DataAjax    = function(opt)
    {
        YF_ListFilter_Interface.apply(this);

        var
        th                  = this,
        _xhr                = null,
        _lastQueryHash      = '';

        this.url            = '';
        this.serverParams   = null;
        this.serverFiltering= 0;
        this.serverFrom     = 0;
        this.serverLimit    = 'limit';
        this.serverQueryCache= 1;
        this.serverResult   = null;


        var
        _getParams          = function(opt)
        {
            var param = {};
            if (th.serverFiltering)
            {
              param[th.serverFiltering] = opt.pattern;
              if (th.serverFrom)
              {
                param[th.serverFrom] = opt.from;
                param[th.serverLimit] = opt.limit;
              }
            }
            (th.serverParams)
              && $.extend(param, ('function' == typeof th.serverParams) ? th.serverParams(th, opt, param) : th.serverParams);
            return param
        },

        _onReady            = function(res)
        {
            th.data = res.data;
            if (th.serverFrom)
                th.total = res.total;
            _xhr = null;
            if('function' == typeof th.serverResult)
                th.serverResult(res);
            
            th.ready()
        };


        this.process        = function(opt)
        {
            var param = _getParams(opt),
                url = ('function' == typeof this.url) ? this.url(this) : this.url;
            if (this.checkLastQuery({data:param, url:url}))
                this.ready();
            else
            {
                this.request(param, url);
                this.setLastQuery({data:param, url:url})
            }
        };

        this.request        = function(param, url)
        {
            _xhr && _xhr.abort();
            _xhr = $.ajax({url     : url,
                            data    : param,
                            dataType: 'json',
                            success : _onReady})
        };

        this.checkLastQuery = function(param)
        {
            return (this.serverQueryCache && _lastQueryHash == this.queryToHash(param))
        };

        this.setLastQuery   = function(param)
        {
            _lastQueryHash = this.queryToHash(param)
        };

        this.queryToHash    = function(param)
        {
            var res = '';
            if (param instanceof Array)
            {
                res = [];
                for (var i = 0; i < param.length; i ++)
                    res.push(this.queryToHash(param[i]));
                res = '[' + res + ']'
            }
            else if (param && 'object' == typeof param)
            {
                res = [];
                for (var i in param)
                    res.push(i + '=' + this.queryToHash(param[i]) + '');
                res = '{' + res + '}'
            }
            else if ('function' == typeof param)
                res = this.queryToHash(param());
            else
                res = String(param);
            return res
        };


        (function(){
            $(['url', 'serverParams', 'serverFiltering', 'serverFrom', 'serverLimit', 'serverResult'])
            .each(function(i, v){
                if (undefined != opt[v])
                    th[v] = opt[v]
            });
            th.serverQueryCache = (undefined == opt.serverQueryCache) ? 1 : opt.serverQueryCache;
            if (th.serverFiltering && 'string' != typeof th.serverFiltering) th.serverFiltering = 'str';
            if (th.serverFrom && 'string' != typeof th.serverFrom) th.serverFrom = 'from';
            if (th.serverParams && ('function' != typeof th.serverParams && 'object' != typeof th.serverParams)) th.serverParams = null;
            if (th.serverResult && ('function' != typeof th.serverResult)) th.serverResult = null;
        })();
    };


    var YF_ListFilter_Interface_Filter  = function(opt)
    {
        YF_ListFilter_Interface.apply(this);


        var
        th                  = this,

        _patternMappers     = {
            'default'     : function(pattern) {
                return pattern
            },
            mutchRegexp   : function(pattern, opt) {
                try {
                    return new RegExp(((opt.fromStart) ? '^' : '') + pattern, (opt.ignoreCase) ? 'i' : '')
                } catch (ex) {
                    return false
                }
            },
            ignoreCase    : function (pattern) {
                return pattern.toLowerCase()
            }
        },
        _itemMutchMappers   = {
            'default'     : function(item, str) {
                return (-1 != item.indexOf(str))
            },
            mutchRegexp   : function(item, str) {
                return (str.test(item))
            },
            insensitive   : function(item, str) {
                return (-1 != item.toLowerCase().indexOf(str))
            },
            fromStart     : function (item, str) {
                return (0 == item.indexOf(str))
            },
            insensitiveFromStart : function (item, str) {
                return (0 == item.toLowerCase().indexOf(str))
            }
        },

        _itemMapper         = null;


        this.data           = null;

        this.fromStart      = 1;
        this.ignoreCase     = 1;
        this.mutchRegexp    = 0;

        this.patternMapper  = null;
        this.itemMutchMapper= null;


        var
        _getPatternMapper   = function(opt)
        {
            return this.patternMapper
              || _patternMappers[
                  (opt.mutchRegexp)
                    ? 'mutchRegexp'
                    : (opt.ignoreCase)
                      ? 'ignoreCase'
                      : 'default'
                ]
        },

        _getItemMutchMapper = function(opt)
        {
            return this.itemMutchMapper
              || _itemMutchMappers[
                  (opt.mutchRegexp)
                    ? 'mutchRegexp'
                    : (opt.ignoreCase)
                      ? (opt.fromStart) ? 'insensitiveFromStart' : 'insensitive'
                      : (opt.fromStart) ? 'fromStart' : 'default'
                ]
        };


        this.process        = function(data, pattern)
        {
            if (data.length > 1 && pattern)
            {
                var opt = {ignoreCase  : this.ignoreCase,
                            mutchRegexp : this.mutchRegexp,
                            fromStart   : this.fromStart,
                            pattern     : pattern},
                    patternMapper = this.patternMapper || _getPatternMapper(opt);
                pattern = patternMapper(pattern, opt);
                // false may return the regexp generation mapper, in that case we should abort filtration
                data = (false === pattern) ? [] : this.filterData(data, pattern, opt)
            }
            this.data = data;
            this.ready()
        };

        this.filterData     = function(data, pattern, opt)
        {
            var result = [],
                itemMutchMapper = _getItemMutchMapper(opt);
            for (var i in data)
                (itemMutchMapper( _itemMapper.toVal(data[i]), pattern, opt, data[i] ))
                  && result.push(data[i]);
            return result
        };


        (function(){
            $(['fromStart', 'ignoreCase', 'mutchRegexp', 'patternMapper', 'itemMutchMapper'])
            .each(function(i, v){
                if (undefined != opt[v])
                    th[v] = opt[v]
            });
            _itemMapper = opt._item
        })();
    };


    var YF_ListFilter_Interface_Render  = function(opt)
    {
        YF_ListFilter_Interface.apply(this);


        var
        _itemMapper         = opt._item;


        this.process        = function(data)
        {
            var result = [];
            for (var i in data)
                result.push(_itemMapper.toNode(data[i], i));
            this.data = result;
            this.ready()
        };
    };


    var YF_ListFilter_Interface_Pager   = function(opt)
    {
        YF_ListFilter_Interface.apply(this);


        var
        th                  = this;


        this.from           = 0;
        this.limit          = 10;
        this.total          = 0;

        this.body           = null;


        var _setFrom        = function(evt)
        {
            var i = evt.data;
            if (i != th.from && i >= 0 && i < th.total)
            {
                th.from = i;
                $(th).trigger('onChange', i)
            }
        };


        this.process        = function(opt)
        {
            $.extend(this, opt);
            this.render();
            $(this.body).empty().append(this.data);
            this.ready()
        };

        this.render         = function()
        {
            var result = [];
            if (this.total > this.limit)
            {
                var node = document.createElement('a');
                $(node).attr({className:'fl_page fl_prev', innerHTML:'&lt;', href:'javascript:;'}).bind('click', this.from - this.limit, _setFrom);
                result.push(node);
                node = document.createElement('span');
                $(node).attr({className:'fl_caption', innerHTML: (this.from + 1) + '-' + Math.min(this.from + this.limit, this.total) + ' of ' + this.total});
                result.push(node);
                node = document.createElement('a');
                $(node).attr({className:'fl_page fl_next', innerHTML:'&gt;', href:'javascript:;'}).bind('click', this.from + this.limit, _setFrom);
                result.push(node);
            }
            this.data = result;
        };

        this.attr           = function(key, val)
        {
            var ret = this;
            switch (key)
            {
                case 'from':
                case 'limit':
                case 'total':
                    if (undefined == val)
                        ret = this[key];
                    else
                        this[key] = val;
                break;

                default:
                    throw 'YF_ListFilter_Interface_Pager > attr : Property ['+key+'] is undefined.';
            }
            return ret
        };


        (function(th){
            $(['from', 'limit', 'total', 'body', 'render'])
            .each(function(i, v){
                if (undefined != opt[v])
                    th[v] = opt[v]
            });
            $(th.body).addClass('fl_pager');
        })(this);
    };


    var YF_ListFilter_Item  = function(opt)
    {
        var
        th                  = this,

        _toValMappers       = {
            'default'           : function(item)
            {
                return String((undefined == (item || {}).val) ? item : item.val)
            },
            combined            : function(item, mapper)
            {
                var res = [];
                for (var i = 0; i < mapper.val.length; i ++)
                    res.push(item[mapper.val[i]]);
                return res.join((undefined == mapper.separator) ? ' ' : mapper.separator)
            },
            mapped              : function(item, mapper)
            {
                return String(item[mapper.val])
            }
        },

        _toKeyMappers       = {
            'default'           : function(item)
            {
                return (undefined == (item || {}).key) ? item : item.key
            },
            mapped              : function(item, mapper)
            {
                return item[mapper.key]
            }
        },

        _toValMapper        = null,
        _toKeyMapper        = null;


        this.itemParamMap   = null;
        this.toValMapper    = null;
        this.toKeyMapper    = null;
        this.toHtmlMapper   = null;

        this.itemTag        = 'div';
        this.itemClass      = 'fl_item';
        this.hlTag          = 'em';
        this.hlClass        = 'fl_mutch';

        this.classHover     = 'fl_hover';


        var
        _onOver             = function()
        {
            //$(this).parent().find('.' + th.classHover).removeClass(th.classHover);
            $(this).addClass(th.classHover)
        },

        _onOut              = function()
        {
        	$(this).removeClass(th.classHover)
        };


        this.toKey          = function(item)
        {
        	return _toKeyMapper(item, this.itemParamMap)
        };

        this.toVal          = function(item)
        {
            
        	var val = _toValMapper(item, this.itemParamMap);
            return val
        };

        this.toHtml         = function(item, key)
        {
            return ( 'function' == typeof this.toHtmlMapper ) ? this.toHtmlMapper(item, this.hlTag, this.hlClass, key) : item;
        }

        this.toNode         = function(item, key)
        {

        	var node = document.createElement(this.itemTag);
            $(node).attr({
                className : this.itemClass,
                innerHTML : this.toHtml(this.toVal(item), key)})
            .bind('mouseover', _onOver)
            .bind('mouseout', _onOut)
            .data('YF_ListFilter_Item', item);
            return node
        };

        this.getItemByNode  = function(node)
        {
        	var item = $(node).data('YF_ListFilter_Item') ||  $(node).parent().data('YF_ListFilter_Item');
            return (undefined != item) ? item : null
        };


        (function(){
            $(['itemParamMap', 'itemTag', 'itemClass', 'hlTag', 'hlClass', 'classHover',
                'toVal', 'toNode', 'toKey', 'toValMapper', 'toKeyMapper', 'toHtmlMapper'])
            .each(function(i, v){
                if (undefined != opt[v])
                    th[v] = opt[v]
            });

            _toValMapper = th.toValMapper || _toValMappers[
                    ('string' == typeof (th.itemParamMap || {}).val)
                        ? 'mapped'
                        : ((th.itemParamMap || {}).val instanceof Array)
                            ? 'combined'
                            : 'default'
                ];
            _toKeyMapper = th.toKeyMapper || _toKeyMappers[
                    ('string' == typeof (th.itemParamMap || {}).key)
                        ? 'mapped'
                        : 'default'
                ];
        })();
    };


    /*
     * @class YF_ListFilter
     * @param {DOMElement} elm
     * @param {Object} opt
     */
    var YF_ListFilter   = function(elm, opt)
    {
        var
        /*
         * Link to self instance to use in private methods
         *
         * @var {YF_ListFilter}
         */
        th                  = this,

        _toSuggest          = 0;


        /*
         * Input element for filtration
         *
         * @var {jQuery}
         * @required
         */
        this.input          = null;

        /*
         * Container for items
         * element selector
         *  or jQuery covered single dom element
         *
         * @var {String|jQuery}
         * @required
         */
        this.container      = '';

        /*
         * Items data source
         * selector of SELECT HTML Element
         *  or array of values
         *  or array of {key:value} pairs
         *  or jQuery covered SELECT HTML Element
         *  or function, that should return one of given types
         *
         * @var {String|Array|Function|jQuery}
         */
        this.source         = '';

        /*
         * Url for ajax request
         * static url
         *  or function for generating an url
         *
         * @var {String|Function}
         */
        this.url            = '';

        /*
         * Filtration process on server side
         * Set the name of parameter with search string for server
         *  Should be set to false for client filtering
         *  or set the name of parameter with search string for server
         *  If set to true, the default name of parameter is "str"
         *
         * @var {Boolean|String}
         */
        this.serverFiltering= 0;

        /*
         * Results paging on server side
         * Set the name of parameter with start result for server
         *  Should be set to false for client paging
         *  or set the name of parameter with start result for server
         *  If set to true, the default name of parameter is "from"
         *
         * @var {Boolean|String}
         */
        this.serverFrom     = 0;

        /*
         * Set the name of parameter with limit for server
         *  Used with "serverFrom" attribute
         *
         * @var {String}
         */
        this.serverLimit    = 'limit';

        /*
         * Additional parameters for server request
         *
         * @var {Object}
         */
        this.serverParams   = null;

        /*
         * Number of items to display per page
         *  Used for paging result items
         *
         * @var {Number}
         */
        this.limit          = 10;

        /*
         * Start show items from position
         *  Used for paging result items
         *
         * @var {Number}
         */
        this.from           = 0;

        /*
         * Pager container or instance
         * element selector
         *  or jQuery covered single dom element
         *  or YF_Paginator setuped instance
         *
         * @var {String|jQuery|YF_Paginator}
         */
        this.pager          = '';

        /*
         * Delay for start search, wait user input
         *
         * @var {Number}
         */
        this.delay          = 220;

        /*
         * Min length of string to start search
         *  
         * @var {Number}
         */
        this.minLength      = 0;

        /*
         * Fill the list at startup
         *
         * @var {Boolean}
         */
        this.fillin         = 0;

        /*
         * Search pattern
         *
         * @var {String}
         */
        this.pattern        = '';

        /*
         * Trim search pattern
         *
         * @var {Boolean}
         */
        this.trim           = 1;

        /*
         * ClassName attribute for selected item's node
         *
         * @var {String}
         */
        this.classSelect    = 'fl_selected';

        /*
         * Selected item
         *
         * @var {Mixed}
         */
        this.selected       = null;

        /*
         * Node of current item
         *
         * @var {DOMElement}
         */
        this.current        = null;

        /*
         * Identifier of selected value
         *
         * @var {String|Number}
         */
        this.key            = 0;

        /*
         * Text value of selected value
         *
         * @var {String}
         */
        this.value          = '';

        /*
         * Html code of "nothing found" message
         *
         * @var {String}
         */
        this.htmlNoData     = '<div>Nothing found</div>';

        /*
         * Data reading interface
         *
         * @var {YF_ListFilter_Interface}
         */
        this._data          = null;

        /*
         * Items filter interface
         *
         * @var {YF_ListFilter_Interface}
         */
        this._filter        = null;

        /*
         * Items render interface
         *
         * @var {YF_ListFilter_Interface}
         */
        this._render        = null;

        /*
         * Pager interface
         *
         * @var {YF_ListFilter_Interface}
         */
        this._pager         = null;

        /*
         * Item adapter
         *
         * @var {YF_ListFilter_Item}
         */
        this._item          = null;

        /**
         * Disable prevent for enter key
         *
         * @var boolean
         */
        this.disablePrevent = false;


        /**
         * @var boolean
         */
        this.disableNoData = false;


        var
        _onKeys             = function(evt)
        {
            var i = false;
            switch (evt.keyCode)
            {
                case 38: // up
                    th.prev();
                break;
                case 40: // down
                    th.next();
                break;

                case 13: // return
                    if(! th.disablePrevent) evt.preventDefault();
                case 9:  // tab
                    th.select();
                break;

                case 33: // page up
                    if (false === i) i = th.from - th.limit;
                case 34: // page down
                    if (false === i) i = th.from + th.limit;
                case 36: // home
                    if (false === i) i = 0;
                case 35: // end
                    if (false === i) i = Math.ceil(th.total/th.limit - 1) * th.limit;
                    if (i >= 0 && i < th.total)
                        _onPagerChange(evt, i);
                    evt.preventDefault(); // its a bug, or its a feature ?)
                break;

                default:
                    clearTimeout(_toSuggest);
                    _toSuggest = setTimeout(_toCallSuggest, th.delay)
            }
        },

        _toCallSuggest      = function()
        {
            th.suggest(th.input.val())
        },

        _onContainerClick   = function(evt)
        {
            th.select(evt.target);
            th.input.focus()
        },

        _onPagerChange      = function(ev, i)
        {
            if (th.from != i)
            {
                th.from = i;
                (th.serverFiltering && th.serverFrom)
                    ? th._data.process(th)
                    : _onDataFiltered()
            }
        },

        _onDataReady        = function()
        {
            var data = th._data.getData();
            //console.info(' > _onDataReady > data : ', data );
            $(th).trigger('onDataReady', [data]);
            (th.serverFiltering)
                ? _onDataFiltered()
                : th._filter.process(data, th.pattern)
        },

        _onDataFiltered     = function()
        {
            var data = (th.serverFiltering)
                        ? th._data.getData()
                        : th._filter.getData();
            //console.info(' > _onDataFiltered > data : ', data );
            if (th._pager)
            {
                th.total = (th.serverFrom) ? th._data.total : data.length;
                if (!th.serverFrom)
                    data = data.slice(th.from, Math.min(th.from + th.limit, th.total))
            }
            $(th).trigger('onDataFiltered', [data]);
            th._render.process(data)
        },

        _onDataRendered     = function()
        {
            var data = th._render.getData();
            //console.info(' > _onDataRendered > data : ', data );
           
            if(data.length && typeof Listing  != "undefined" /*&& this.input.search("input#lf_primary_keyword_keyword") != -1*/){
            	var spanTag = document.createElement("span");
        		spanTag.className ="recom-keyword";
            	data.unshift(spanTag);
            }
            if(data.length)
            {
                th.container.empty().removeClass('fl_container_hidden').addClass('fl_container_visible').append(data);
            }
            else
            {
                if(th.disableNoData)
                    th.container.empty().removeClass('fl_container_visible').addClass('fl_container_hidden');
                else
                    th.container.empty().removeClass('fl_container_hidden').addClass('fl_container_visible').append(th.htmlNoData);
            }

            (th._pager) && th._pager.process({total: th.total, from:th.from});
            $(th).trigger('onDataRendered', [data]);
            _setBusyState(0)
        },

        _prevOrNext         = function(isPrev)
        {
            if (th.current)
                th.current = th.current[ (isPrev) ? 'prev' : 'next' ]();
            if (!th.current || !th.current.length)
                th.current = th.container.find('.' + th._item.itemClass + ':' + ((isPrev) ? 'last' : 'first'));
            th.container.find('.'+th._item.classHover).removeClass(th._item.classHover);
            th.current.addClass(th._item.classHover);

            var scrollTop = th.container.scrollTop(),
                heightHalf = th.container.outerHeight() / 2,
                position = th.current.offset().top - th.container.offset().top;
            if (position > heightHalf / 2)
                th.container.scrollTop( scrollTop + position );
            else if (position < 0)
                th.container.scrollTop( scrollTop + position - heightHalf/2 )
        },

        _setBusyState       = function(isBusy)
        {
            (isBusy)
                ? th.container.addClass('fl_busy')
                : th.container.removeClass('fl_busy')
        };


        this.suggest        = function(str, force)
        {
            if (true === str)
            {
                force = 1;
                str = ''
            }
            else
            {
                this.input.val(str)
            }
            if (str.length < this.minLength)
                return ;
            if (this.trim)
                str = $.trim(str);
            if (force || str != this.pattern)
            {
                _setBusyState(1);
                this.selected = null;
                this.current = null;
                this.key = 0;
                this.value = '';
                this.pattern = str;
                this.from = 0;
                this._data.process(this)
            }
        };

        this.prev           = function()
        {
            _prevOrNext(1)
        };

        this.next           = function()
        {
            _prevOrNext()
        };

        this.select         = function(target)
        {
            var item = this._item.getItemByNode(target || this.current);
            if (item)
            {
                if (target)
                    this.current = $(target);
                this.container.find('.'+this.classSelect).removeClass(this.classSelect);
                this.container.find('.'+this._item.classHover).removeClass(this._item.classHover);
                this.current.addClass(this.classSelect);
                this.selected = item;
                this.value = this.pattern = this._item.toVal(item);
                this.key = this._item.toKey(item);
                this.input.val(this.pattern);
                //onchange for preview
             
                if(typeof Listing  != "undefined" ){
                  
                   var check;
                   check = $(this.input).attr("name") == 'lf[primary_keyword][keyword]';

                  
                   if(check){
                 
                       if(typeof Listing.LINFO != "undefined"){
                          Listing.LINFO['keyword']=this.pattern;
                          rebuildPreviewAddress();
                       }
                       $('#suggest_lf_primary_keyword').hide();
                       $('#pager_lf_primary_keyword').hide();
                   }        
                }
                //
                $(th).trigger('onSelect', [this.key, this.value, this.selected]);
            }
        };

        this.onSelect       = function(evt, key, val, item){};

        this.val            = function()
        {
            return this.selected || null
        };


        (function(th){
            opt = opt || {};
            $.extend(th, opt);

            if ($.nodeName(elm, 'input'))
            {
                th.input = $(elm)
            }
            else if ($.nodeName(elm, 'select'))
            {
                th.source = $(elm).hide();
                var id = th.source.attr('id');
                th.input = $('<input type="text"/>')
                    .addClass(th.source.attr('class'))
                    .attr('tabindex', th.source.attr('tabindex'));
                if (id)
                {
                    $("label[for='"+id+"']").attr('for', 'ac_' + id)
                }
                else
                {
                    id = Number(new Date())
                }
                th.input.attr('id', 'ac_' + id);
                th.source.after(th.input)
            }
            else
            {
                throw 'YF_ListFilter > construct : Unable to construct. No valid input field given.'
            }
            th.input.bind(($.browser.mozilla) ? 'keypress' : 'keydown', _onKeys);

            th.container = ('string' == typeof opt.container)
                ? $(opt.container)
                : opt.container || null;
            th.container.addClass('fl_conteiner').click(_onContainerClick);

            th._item = new YF_ListFilter_Item(th);

            th._data = new ((opt.url)
                ? YF_ListFilter_Interface_DataAjax
                : ('function' == typeof opt.source)
                    ? YF_ListFilter_Interface_DataFunction
                    : YF_ListFilter_Interface_DataStatic)(th);
            $(th._data).bind('onReady', _onDataReady);

            if (!opt.serverFiltering)
            {
                th._filter = new YF_ListFilter_Interface_Filter(th);
                $(th._filter).bind('onReady', _onDataFiltered)
            }

            th._render = new YF_ListFilter_Interface_Render(th);
            $(th._render).bind('onReady', _onDataRendered);

            if (opt.pager)
            {
                var arg = {limit:th.limit, from:th.from};
                if ('object' == typeof opt.pager) arg = $.extend(arg, opt.pager);
                else if ('string' == typeof opt.pager) arg.body = opt.pager;
                th._pager = new YF_ListFilter_Interface_Pager(arg);
                $(th._pager).bind('onChange', _onPagerChange)
            }


            if(th.onSelect != 'undefined')
                $(th).bind('onSelect', th.onSelect);
            else
                th.onSelect = null;

            (th.fillin) && th.suggest(true)
        })(this);
    };


    /*
     * Utilitary method for creating and adding YF_ListFilter to the current element
     *
     * @param {Object} opt options
     */
    var _process        = function(opt)
    {
        var ret = null;
        if ($.nodeName(this, 'input') || $.nodeName(this, 'select'))
        {
            ret = $(this).data('yf_listfilter');
            if (!ret)
            {
                ret = new YF_ListFilter(this, opt);
                $(this).data('yf_listfilter', ret)
            }
        }
        return ret
    };

    
    /*
     * jQuery plugin for creating YF_ListFilter for given INPUT/SELECT element[s]
     *
     * @param {Object} opt options for constructor
     */
    $.fn.YF_listFilter  = function(opt)
    {
        if (this.length == 1)
        {
            return _process.apply(this.get(0), arguments)
        }
        else if (this.length > 0)
        {
            this.each(_process, arguments)
        }
        return this
    };
})(jQuery);

