/**
 * Objects list
 */
(function($) {
  
  /**
   * Public methods
   * 
   * @var Object
   */
  var public_methods = {
      
    /**
     * Initialise Objects list
     * 
     * @param Object options
     *      items - items for the list
     *      grouping - grouping cases
     *      filtering - filtering cases
     *      render_item - how to render item
     *      search_index - function to extract search index from item
     * @returns jQuery
     */
    init : function(options) {
      return this.each(function () {
        var _this = this;
        var $this = $(this);
        var variables = _variables($this);
        var storage = _storage($this);
                
        variables.settings = $.extend({}, settings, options || {});
        variables.properties = [];
        
        draw_initial_wireframe.apply(_this);
        
        if (variables.settings.filtering && variables.settings.filtering.length) {
          init_filtering.apply(_this);
        } // if
        
        if (variables.settings.grouping && variables.settings.grouping.length) {
          init_grouping.apply(_this);
        } // if
        
        if (variables.settings.reorder_url) {
          init_reordering.apply(_this);
        } // if
        
        if (variables.settings.additional_buttons) {
          init_additional_buttons.apply(_this);
        } // if
        
        if (variables.settings.print_url) {
          init_printing.apply(_this);
        } // if
        
        init_options_and_search.apply(_this);
                
        // Process initial items and render each one
        storage.items = $('<div></div>');
        if (typeof(variables.settings.items) == 'object' && variables.settings.items) {
          import_items.apply(_this, [variables.settings.items]);
        } // if
        
        // init viewing
        init_viewing.apply(_this);
                
        // free up some memory
        delete variables.settings.items;
        
        // initial refresh
        update_items_list.apply(_this);

        // determine id
        if (!variables.settings.id) {
          variables.settings.id = 'objects_list' + $.now();
        } // if

        // store the variables
        variables.settings.page_url = window.location.href;
        variables.settings.page_title = App.Wireframe.PageTitle.get();

        // determine if we are using touch device
        variables.settings.is_touch_device =  !!('ontouchstart' in window) ? 1 : 0;

        var empty_wrapper_links = $('.objects_list_details_empty_wrapper .objects_list_details_actions a');
        if (empty_wrapper_links) {
          if (empty_wrapper_links.length === 1) {
            empty_wrapper_links.addClass('single');
          } else {
            empty_wrapper_links.first().addClass('first');
            empty_wrapper_links.last().addClass('last');
          } // if
        } // if

        // init history handling
        init_history.apply(_this);
      });
    },    
    
    /**
     * Provides public function for adding items to the list
     * 
     * @param Object item
     * @param Boolean prepare_item
     * @param Boolean skip_loading
     * 
     * @return jQuery
     */
    add_item : function (item, prepare_item, skip_loading) {
      prepare_item = prepare_item === undefined ? true : prepare_item;
      
      return this.each(function () {
        add_item.apply(this, [item, prepare_item, skip_loading]);
      });
    },
    
    /**
     * Provides public function for adding items to the list
     * 
     * @param Object item
     * @param Boolean prepare_item
     * @param mixed after_id
     * 
     * @return jQuery
     */
    add_item_after : function (item, prepare_item, after_id) {
      prepare_item = prepare_item === undefined ? true : prepare_item;
      
      return this.each(function () {
        if (!after_id) {
          add_item.apply(this, [item]);
        } else {
          add_item_after.apply(this, [item, prepare_item, after_id]);
        } // if
      });
    },
    
    /**
     * Provides public function for updating items in the list
     * 
     * @param Object item
     * @param Boolean prepare_item
     * 
     * @return jQuery
     */
    update_item : function (item, prepare_item) {
      prepare_item = prepare_item === undefined ? true : prepare_item;
      
      return this.each(function () {
        update_item.apply(this, [item, prepare_item]);
      });
    },
    
    /**
     * Provides public method for deleting items
     * 
     * @param Object item - can be item_id or it can be object deleted itself
     * @param Boolean prepare_item
     * 
     * @returns jQuery
     */
    delete_item : function (item, prepare_item) {
      prepare_item = prepare_item === undefined ? true : prepare_item;
      
      return this.each(function () {
        delete_item.apply(this, [item, prepare_item]);
      });
    },
    
    /**
     * Public method for adding item in the grouping_id map
     * 
     * @param String grouping_id
     * @param Strubg item_id
     * @param Mixed item
     * @return jQuery
     */
    grouping_map_add_item : function (grouping_id, item_id, item) {
      return this.each(function () {
        grouping_map_add_item.apply(this, [grouping_id, item_id, item]);
      });
    },
    
    /**
     * Public method for updating items in grouping_id map
     * 
     * @param String grouping_id
     * @param String item_id
     * @param Mixed item
     * @return jQuery
     */
    grouping_map_update_item : function (grouping_id, item_id, item) {
      return this.each(function () {
        grouping_map_update_item.apply(this, [grouping_id, item_id, item]);
      });
    },

    /**
     * Public method for deleting item from the grouping_id map
     * 
     * @param String grouping_id
     * @param String item_id
     * @param Mixed item
     * @return jQuery
     */
    grouping_map_delete_item : function (grouping_id, item_id) {
      return this.each(function () {
        grouping_map_delete_item.apply(this, [grouping_id, item_id]);
      });      
    },
    
    /**
     * Public method for replacing grouping_id map
     * 
     * @param String grouping_id
     * @param Object new_map
     * @return jQuery
     */
    grouping_map_replace : function (grouping_id, new_map) {
      return this.each(function () {
        grouping_map_replace.apply(this, [grouping_id, new_map]);
      });
    },

    /**
     * Load empty screen
     *
     * @return jQuery
     */
    load_empty : function () {
      return this.each(function () {
        load_empty.apply(this);
      });
    },
    
    /**
     * Public method for selecting the item
     * 
     * @param mixed item_id
     */
    load_item : function (item_id, permalink) {
      return this.each(function () {
        load_item.apply(this, [item_id, permalink]);
      });
    },
    
    /**
     * Public method for selecting the gruop
     * 
     * @param mixed item_id
     */
    load_group : function (grouping_id, group_id, permalink) {
      return this.each(function () {
        load_group.apply(this, [grouping_id, group_id, permalink]);
      });
    },
    
    /**
     * Return selection of certain objects list
     * 
     * @returns Array
     */
    get_selection : function () {
      return get_selection.apply(this[0]);
    },
    
    /**
     * Get item by id
     * 
     * @param id
     * @returns jQuery
     */
    get_item : function (id) {
      return get_item.apply(this[0], [id]);
    },

    /**
     * Check if item with id is selected
     *
     * @param id
     * @return boolean
     */
    is_selected : function (id) {
      return is_selected.apply(this[0], [id]);
    },

    /**
     * Check if item is currently loaded
     *
     * @param mixed id
     * @param boolean is_group
     * @return boolean
     */
    is_loaded : function (id, is_group) {
      return is_loaded.apply(this[0], [id, is_group]);
    },
    
    /**
     * Refresh the objects list
     * 
     * @returns jQuery
     */
    refresh : function () {
      return this.each(function () {
        refresh.apply(this);
      });
    },

    /**
     * Reloads the selected item
     *
     * @returns null
     */
    reload_current : function () {
      return reload_current.apply(this[0]);
    }
  };
  
  /**
   * Import items
   * 
   * @param Array items
   */
  var import_items = function (items) {
    var _this = this;
    var $this = $(this);
    var storage = _storage($this);
    
    storage.items.empty();
    
    $.each(items, function(item_index, item_value) {
      storage.items.append(render_item.apply(_this, [item_value]));
    });
  }; // import_items
  
  /**
   * Check if item matches requirements
   * 
   * @param Object item
   * @return boolean
   */
  var item_matches_requirements = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (!variables.settings.requirements || $.isEmptyObject(variables.settings.requirements)) {
      return true;
    } // if
    
    var matches_requirements = true;
    
    $.each(variables.settings.requirements, function (requirement_name, requirement_value) {
      if (item[requirement_name] != requirement_value) {
        matches_requirements = false;
        return false;
      } // if
    });
    
    return matches_requirements;
  }; // item_matches_requirements

  /**
   * Initialize history handling
   */
  var init_history = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    App.Wireframe.Events.bind('history_state_changed.content', function (event) {
      var state = History.getState();
      var handler = state.data['handler'];
      var handler_id = state.data['handler_id'];

      // this is not handler we're looking for
      if (handler != 'objects_list') {
        return true;
      } // if

      // this is some other handler of same type
      if (variables.settings.id != handler_id) {
        return true;
      } // if

      var url = state['url'];
      var item = state.data['additional'] ? state.data['additional']['item'] : null;

      if (!item) {
        load_empty.apply(_this);
      } else if (item.type == 'item') {
        load_item.apply(_this, [item.id, url]);
      } else {
        load_group.apply(_this, [item.grouping, item.id, url]);
      } // if

      History.handled = true;
      return false;
    });
  }; // init_history
  
  /**
   * Initialize listening
   */
  var init_listening = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
  
    // bind add events
    if (variables.settings.listen.add && variables.settings.listen.add.length) {
      var bind = [];
      $.each(variables.settings.listen.add, function (index, event_name) {
        bind.push($.trim(event_name + '.content'));
      });
      
      App.Wireframe.Events.bind(bind.join(' '), function (event, item) {
        add_item.apply(_this, [event, item]);        
      });
    }; // if
    
    // bind update events
    if (variables.settings.listen.update && variables.settings.listen.update.length) {
      var bind = [];
      $.each(variables.settings.listen.update, function (index, event_name) {
        bind.push($.trim(event_name + '.content'));
      });
      App.Wireframe.Events.bind(bind.join(' '), function (event, item) {
        update_item.apply(_this, [event, item]);
      });
    }; // if 
    
    // bind delete events
    if (variables.settings.listen['delete'] && variables.settings.listen['delete'].length) {
      var bind = [];
      $.each(variables.settings.listen['delete'], function (index, event_name) {
        bind.push($.trim(event_name + '.content'));
      });
      App.Wireframe.Events.bind(bind.join(' '), function (event, item) {
        delete_item.apply(_this, [event, item]);
      });      
    }; // if
  }; // init_listening
  
  /**
   * Test item for required fields defined by the list
   * 
   * @param Object item
   * @return boolean
   */
  var check_fields_requirements = function (item, skip_warning) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    if (variables.settings.required_fileds && variables.settings.required_fileds.length) {
      var missing_fields = [];
      $.each(variables.settings.required_fileds, function (index, field) {
        if (item[field] === undefined) {
          missing_fields.push(field);
        } // if
      });
      
      if (missing_fields.length) {        
        if (!skip_warning) {
          console.error("Cannot add item to the objects list. These fields are missing: " + missing_fields.join(", "));
        } // if

        return false;
      } // if
    } // if 
    
    return true;
  }; // check_fields_requirements
  
  /**
   * Load item even if it's not visible 
   * 
   * @param mixed item_id
   */
  var load_item = function (item_id, permalink) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var selected_item = variables.objects_list_body.find('tr.objects_list_item[item_id="' + item_id + '"]');
    if (!selected_item.length) {
      selected_item = get_item.apply(_this, [item_id]);
    } // if
    
    variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
    variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
    
    if (!$.isEmptyObject(selected_item) && selected_item.length) {
      do_load_single.apply(_this, [selected_item], true);
    } else {
      do_load_single.apply(_this, [permalink, item_id, false], true);
    } // if
    
    if (selected_item.is(':visible')) {
      selected_item.addClass('selected');
      save_selection.apply(_this);
    } // if
  }; // load_item
  
  /**
   * Load group even if it's not visible
   * 
   * @param mixed item_id
   * @param mixed grouping_id
   */
  var load_group = function (grouping_id, group_id, permalink) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var selected_item = null;
    if (variables.active_grouping == grouping_id) {
      selected_item = variables.objects_list_body.find('div.object_list_group_head.clickable[item_id="' + group_id + '"]');
    } // if
    
    if (!selected_item || !selected_item.length) {
      var grouping = get_grouping.apply(_this, [grouping_id]);    
    } // if
    
    variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
    variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
    
    if (!$.isEmptyObject(selected_item) && selected_item.length) {
      do_load_single.apply(_this, [selected_item], true);
    } else {
      do_load_single.apply(_this, [permalink, group_id, true], true);
    } // if

    if (selected_item && selected_item.is(':visible')) {
      selected_item.addClass('selected');
      save_selection.apply(_this);
    } // if
  }; // load_group
  
  /**
   * select item with item_id
   * 
   * @param mixed item_id
   */
  var select_item = function (item_id) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    variables.objects_list_body.find('tr.objects_list_item[item_id="' + item_id + '"]').click();
  }; // select_item
  
  /**
   * Select group
   * 
   * @param String grouping_id
   * @param mixed group_id
   */
  var select_group = function (grouping_id, group_id) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (!variables.active_grouping == grouping_id) {
      return false;
    } // if
    
    variables.objects_list_body.find('div.object_list_group_head[group_id="' + group_id + '"]').click();
  }; // select_group
  
  /**
   * Prepare item for list
   */
  var prepare_item_for_list = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    if (variables.settings.prepare_item && typeof(variables.settings.prepare_item) == 'function') {
      item = variables.settings.prepare_item.apply(_this, [item]);
    }; // if
    
    return item;
  }; // prepare_item_for_list

  /**
   * Add item to the list
   *
   * @param Object item
   * @param Boolan prepare_item
   * @param Boolean skip_loading
   */
  var add_item = function (item, prepare_item, skip_loading) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);
    
    if (prepare_item) {
      item = prepare_item_for_list.apply(_this, [item]);
    } // if
           
    if (!check_fields_requirements.apply(_this, [item])) {
      return false;
    } // if
    
    if (!item_matches_requirements.apply(this, [item])) {
      return false;
    } // if
           
    storage.items.append(render_item.apply(_this, [item]));
    update_items_list.apply(_this);
    
    if (!skip_loading) {
      select_item.apply(_this, [item.id]);      
    } // if
  }; // add_item
  
  /**
   * Add item to the list after after_id
   *
   * @param Object item
   * @param Boolean prepare_item
   * @param mixed after_id
   */
  var add_item_after = function (item, prepare_item, after_id) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);
    
    if (prepare_item) {
      item = prepare_item_for_list.apply(_this, [item]);
    } // if
           
    if (!check_fields_requirements.apply(_this, [item])) {
      return false;
    } // if
    
    if (!item_matches_requirements.apply(this, [item])) {
      return false;
    } // if
    
    var rendered_item = render_item.apply(_this, [item]);
    var wanted_element = storage.items.find('tr[item_id="' + after_id + '"]');
    
    if (wanted_element.length) {
      wanted_element.after(rendered_item);
    } else {
      storage.items.append(rendered_item);
    } // if
    
    update_items_list.apply(_this);   
    select_item.apply(_this, [item.id]);
  }; // add_item
  
  /**
   * Update item in the list
   *
   * @param Object item
   * @param Boolean prepare_item
   * @param Boolean skip_refresh
   */
   var update_item = function (item, prepare_item, skip_refresh) {
     var _this = this;
     var $this = $(this);
     var variables = _variables($this);
     var storage = _storage($this);

     var current_item = storage.items.find('tr[item_id=' + item.id + ']');

     if (prepare_item) {
       item = prepare_item_for_list.apply(_this, [item]);
     } // if

     if (!check_fields_requirements.apply(_this, [item])) {
       return false;
     } // if
     
     if (!item_matches_requirements.apply(this, [item])) {
       delete_item.apply(this, [item]);
       load_empty.apply(this);
     } // if

     if (current_item.length) {
       current_item.after(render_item.apply(_this, [item]));
       current_item.remove();

       if (!skip_refresh) {
         update_items_list.apply(_this);
       } // if
     } else {
       add_item.apply(_this, [item, false, true]);
     } // if
   }; // update_item
   
   /**
    * Get item
    * 
    * @param Number id
    * @return DOMNode
    */
   var get_item = function (id) {
     var _this = this;
     var $this = $(this);
     var variables = _variables($this);
     var storage = _storage($this);     

     var current_item = storage.items.find('tr[item_id=' + id + ']');
     return current_item;
   }; // get_item
  
  /**
   * Delete item from the list
   *
   * @param Object item
   * @param Boolean prepare_item
   */
  var delete_item = function (item, prepare_item, skip_refresh) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);     
    
    var item_id = null;
    
    if (typeof(item) == 'object') {
      if (prepare_item) {
        item = prepare_item_for_list.apply(_this, [item]);
      } // if

      item_id = item['id'];
    } else {
      item_id = item;
    } // if
    
    if (!item_id) {
      console.error("Item you provided has no valid id");
    } // if

    var current_item = storage.items.find('tr[item_id=' + item_id + ']');
    if (current_item.length) {
      current_item.remove();
      if (!skip_refresh) {
        update_items_list.apply(_this);
      } // if
    }; // if  
  }; // delete_item
  
  /**
   * Add item to the grouping_map
   * 
   * @param String grouping_id - id of the grouping map
   * @param Mixed item_id - id of the item
   * @param Mixed item - item itself
   */
  var grouping_map_add_item = function (grouping_id, item_id, item) {
    if (!grouping_id || !item_id || !item) {
      return false;
    } // if
        
    var grouping = get_grouping.apply(this, [grouping_id]);
    if (!grouping) {
      return false;
    } // if
    
    grouping.map[item_id] = item;
    update_items_list.apply(this);
        
    return true;
  }; // grouping_map_add_item
  
  /**
   * Update item in the grouping_map
   * 
   * @param String grouping_id - id of the grouping map
   * @param Mixed item_id - id of the item
   * @param Mixed item - item itself
   */
  var grouping_map_update_item = function (grouping_id, item_id, item) {
    if (!grouping_id || !item_id || !item) {
      return false;
    }
    
    var grouping = get_grouping.apply(this, [grouping_id]);
    if (!grouping) {
      return false;
    } // if
    
    grouping.map[item_id] = item;
    update_items_list.apply(this);
    
    return true;
  }; // grouping_map_update_item
  
  /**
   * Delete item from the grouping_map
   * 
   * @param String grouping_id - id of the grouping map
   * @param Mixed item_id - id of the item
   * @param Mixed item - item itself
   */
  var grouping_map_delete_item = function (grouping_id, item_id) {
    if (!grouping_id || !item_id) {
      return false;
    } // if
    
    var grouping = get_grouping.apply(this, [grouping_id]);
    if (!grouping) {
      return false;
    } // if
    
    delete grouping.map[item_id];
    update_items_list.apply(this);
    
    return true;
  }; // grouping_map_delete_item
  
  /**
   * replace existing
   * 
   * @param String grouping_id - id of the grouping map
   * @param Object new_map - new map which will replace the existing map
   */
  var grouping_map_replace = function (grouping_id, new_map) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
  	var grouping = get_grouping.apply(this, [grouping_id]);
  	if (!grouping) {
      return false;
    } // if

    grouping.map = new_map;
    update_items_list.apply(this);

    // enable/disable grouping control dependable of whether mapping exists
    var grouping_control = variables.objects_list_options.find('li.object_list_grouping input[type="radio"][name="grouping"][value="' + grouping_id + '"]');
    var grouping_control_wrapper = grouping_control.parents('li:first');
    
    // if mapping is empty we need to disable grouping we are updating
    if ($.isEmptyObject(new_map)) {      
      grouping_control.attr('disabled', true);
      grouping_control_wrapper.addClass('disabled');      

      // if map that is replacing is current one and new mapping is not valid, we need to select first other grouping which is not disabled
      if (grouping_control.is(':checked')) {
        var first_available_grouping = variables.objects_list_options.find('li.object_list_grouping input[type="radio"][name="grouping"]:not(:disabled):first').click();
      } // if
    } else {
      grouping_control.attr('disabled', false);
      grouping_control_wrapper.removeClass('disabled');      
    } // if


    // try to find control in mass form that also needs to have it's fields updated
    var mass_control = variables.multi_form.find('select[name="' + grouping_id + '"]');
    if (mass_control.length) {
      var current_value = mass_control.val();
      mass_control.find('option').each(function () {
        var option = $(this);
        if (option.attr('value') != '' && option.attr('value') != 0) {
          option.remove();
        } // if
      });

      $.each(new_map, function (value, title) {
        mass_control.append('<option value="' + value + '">' + title + '</option>');
      });

      if (!current_value) {
        mass_control.find('option').each(function () {
          var option = $(this);
          if (option.attr('value') != '' && option.attr('value') != 0) {
            option.attr('selected', true);
            return false;
          } // if
        });
      } else {
        mass_control.val(current_value)
      }
    } // if
    
    return true;
  }; // grouping_map_update_item
  
  /**
   * get grouping by ID
   * 
   * @param String grouping_id
   * @return Object
   */
  var get_grouping = function (grouping_id) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    for (var index in variables.settings.grouping) {
      if (variables.settings.grouping[index].property == grouping_id) {
        return variables.settings.grouping[index];
      } // if
    } // for
    
    return false;
  }; // get_grouping
  
  /**
   * Initialize empty_page, single_pages, multiselect
   */
  var init_viewing = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
        
    load_empty.apply(_this);
    
    variables.active_selection = [];
    
    variables.objects_list_body.delegate("tr.objects_list_item", "click", function(event){
      var anchor = $(event.target).parents('*').andSelf().filter('a:first');
      
      if (!anchor.length) {
        click_item.apply(_this, [event, $(this)]);        
      } // if
    });
    
    variables.objects_list_body.delegate('div.object_list_group_head.clickable', 'click', function (event) {
      click_group.apply(_this, [event, $(this)]);
    });    
  }; // init_viewing
  
  /**
   * Handle item clicking
   * 
   * @param Object event
   * @param jQuery row
   */
  var click_item = function (event, row) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if ($this.is('.reorder_mode')) {
      return true;
    } // if

    // if it's a touch device we don't need to focus the filter field
    if (!variables.settings.is_touch_device) {
      variables.objects_list_search_filter.focus();
    } // if
    
    // remove selected groups
    variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
    
    var ctrlKey = event.ctrlKey;
    if ($.platform.mac) {
      ctrlKey = event.metaKey;
    } // if
    
    // if CMD key is used
    if (ctrlKey) {
      if (row.is('.selected')) {
        row.removeClass('selected');
        trigger_event.apply(_this, ['item_deselected', [row]]);
        if (variables.active_selection.length == 1) {
          load_empty.apply(_this);
        } else if (variables.active_selection.length == 2) {
          var current_item = variables.objects_list_body.find('tr.objects_list_item.selected:first');
          load_single.apply(_this, [current_item]);
        } else {
          load_multi.apply(_this);
        } // if        
      } else {

        row.addClass('selected');
        trigger_event.apply(_this, ['item_selected', [row]]);
        if (variables.active_selection.length == 0) {
          var current_item = variables.objects_list_body.find('tr.objects_list_item.selected:first');
          load_single.apply(_this, [current_item]);
        } else {
          load_multi.apply(_this);          
        } // if
      } // if
      
    // if there were no key modifiers
    } else {
      if (!row.is('.selected') || variables.active_selection.length > 1) {
        variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
        row.addClass('selected');
        mark_single_row.apply(_this,[row]);
        trigger_event.apply(_this, ['item_selected', [row]]);
        load_single.apply(_this, [row]);
      } else {
        variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
        trigger_event.apply(_this, ['item_deselected', [row]]);
        load_empty.apply(_this);
      } // if
    } // if

    save_selection.apply(_this);
  }; // click_item
  
  /**
   * Handle clicking on the group
   */
  var click_group = function (event, group) {
    // multiple groups cannot be selected
    var ctrlKey = event.ctrlKey;
    if ($.platform.mac) {
      ctrlkey = event.metaKey;
    } // if
    
    // if CMD key is used
    if (ctrlKey) {
      return false;
    } // if
    
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    // remove selected items
    variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
    variables.active_selection = [];
    
    if (group.is('.selected')) {
      variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
      load_empty.apply(_this);
    } else {
      variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
      group.addClass('selected');
      load_single.apply(_this, [group]);
    } // if
  }; // click_group
  
  /**
   * Save seleection
   */
  var save_selection = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    variables.active_selection = [];

    var selected_items = variables.objects_list_body.find('tr.objects_list_item.selected');
    selected_items.each(function () {
      variables.active_selection.push(this.getAttribute('item_id'));
    });

    variables.active_group_selection = null;
    var selected_group = variables.objects_list_body.find('.object_list_group_head.clickable.selected:first');
    if (selected_group.length) {
      variables.active_group_selection = selected_group.attr('item_id');
    } // if
    
    variables.settings.selected_items_num = selected_items.length;
    variables.multi_title.html(App.lang(variables.settings.multi_title, {num : selected_items.length}));
  }; // save_selection
  
  /**
   * Restore selection
   */
  var restore_selection = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (variables.active_selection && variables.active_selection.length) {
      var query = [];
      $.each(variables.active_selection, function (index, item_id) {
        query.push('tr[item_id=' + item_id  + ']');
      });
      
      var matching_objects = variables.objects_list_body.find(query.join(',')).addClass('selected');
      
      if (matching_objects.length == 0) {
        if (!variables.view_wrapper.is('.empty') && !variables.view_wrapper.is('.single')) {
          load_empty.apply(_this);
        } // if
      } else if (matching_objects.length == 1) {
        if (!variables.view_wrapper.is('.single')) {
          load_single.apply(_this, [matching_objects]);
        } // if
      } else {
        if (!variables.view_wrapper.is('.multi')) {
          load_multi.apply(_this);
        } // if
      } // if
      
      save_selection.apply(_this);
    } else if (variables.active_group_selection) {
      var selected_group = variables.objects_list_body.find('.object_list_group_head.clickable[item_id="' + variables.active_group_selection + '"]');
      if (selected_group.length) {
        selected_group.addClass('selected');

        if (!variables.view_wrapper.is('.single')) {
          load_single.apply(_this, [selected_group]);
        } // if
      } // if

    } // if
  }; // restore_selection

  /**
   * Return current selection in form of item ids
   * 
   * @return Array
   */
  var get_selection = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    return variables.active_selection;
  }; // get_selection
  
  /**
   * Load empty wrapper
   */
  var load_empty = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    App.Wireframe.Events.unbind('.single');
    App.Wireframe.Events.unbind('.inline_tab');

    window.soft_refreshed = false;

//    History.pushState({
//      'handler' : 'objects_list',
//      'handler_id' : variables.settings.id
//    }, variables.settings.page_title, variables.settings.page_url);

    variables.loaded_item = null;

    variables.view_wrapper.removeClass('multi single').addClass('empty');
    variables.empty_wrapper.show();
    variables.single_wrapper.empty().hide();
    variables.multi_wrapper.hide();

    variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
    
    App.Wireframe.Events.restore(variables.empty_page_events);
    
    trigger_event.apply(_this, ['empty']);
    
    cancel_single_loading.apply(_this);
  }; // load_empty_wrapper
  
  /**
   * Load single url
   *
   * @param String url
   */
  var load_single = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var item_id = item.attr('item_id');
    var url = item.attr('permalink');
    
    if (item.is('tr')) {
      var parsed_item = {'id' : item_id, 'type' : 'item'};
    } else {
      var parsed_item = {'id' : item_id, 'type' : 'group', 'grouping' : item.attr('grouping_property')};
    } // if

    window.soft_refreshed = false;
       
    History.pushState({
      'handler' : 'objects_list',
      'handler_id' : variables.settings.id,
      'additional' : {
        'item'  : parsed_item
      }
    }, App.lang('Loading'), item.attr('permalink'));

    if (variables.item_currently_loading != item_id) {
      do_load_single.apply(_this, [item]);
    } // if
  }; // load_single_url
  
  /**
   * Load the single page
   */
  var do_load_single = function (item, item_id, is_group) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    if (typeof(item) == 'object') {
      var item_id = item.attr('item_id');
      var is_group = !item.is('tr');
      var url = item.attr('permalink');
    } else {
      var item_id = item_id;
      var is_group = is_group;
      var url = item;
    } // if
    
    cancel_single_loading.apply(_this);
    
    variables.item_currently_loading = item_id;
    
    variables.loader_overlay = $('<div class="inline_loader"><div class="inline_loader_inner"></div><div class="inline_loader_bottom">' + App.lang('Loading') + ' ...</div></div>').insertAfter(variables.single_wrapper);
    variables.loader = $.ajax({
      'url' : App.extendUrl(url, {'single' : 1}),
      'type' : 'get',
      'success' : function (response) {
        App.Wireframe.Events.unbind('.single');
    
        variables.view_wrapper.removeClass('multi empty').addClass('single');
        variables.empty_wrapper.hide();
        variables.multi_wrapper.hide();
        variables.single_wrapper.empty().show().html(response);
        
        if (item_id !== false) {
          if (is_group) {
            var item_loaded = variables.objects_list_body.find('div.object_list_group_head[item_id="' + item_id + '"]');
          } else {
            var item_loaded = variables.objects_list_body.find('tr.objects_list_item[item_id="' + item_id + '"]');
          } // if          
         
          trigger_event.apply(_this, ['single_loaded', [item_loaded.eq(0)]]);
        } else {
          // @todo what now
          trigger_event.apply(_this, ['single_loaded', [null]]);
        } // if
                
        cancel_single_loading.apply(_this);

        variables.loaded_item = {
          'item_id' : item_id,
          'is_group' : is_group,
          'url' : url
        };

        App.Wireframe.Events.trigger('single_content_updated', [ variables.single_wrapper[0], {
          'item_id' : item_id,
          'is_group' : is_group,
          'url' : url
        }]);
      },
      'error' : function (response, response_text) {
        trigger_event.apply(_this, ['single_loaded', [variables.objects_list_body.find('tr.selected').eq(0)]]);          
        
        cancel_single_loading.apply(_this);
      }
    });
    
    trigger_event.apply(_this, ['single']);
  }; // do_load_single
  
  /**
   * If single page is loading, stop it
   */
  var cancel_single_loading = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
        
    if (variables.loader_overlay && variables.loader_overlay.length) {
      variables.loader_overlay.remove();
    } // if
    
    variables.item_currently_loading = null;
    
    if (variables.loader) {
      // abort the request if we can
      variables.loader.abort();    
      // unset the ajax request
      variables.loader = null;
    } // if
  };
  
  /**
   * Display page for multi page
   */
  var load_multi = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    App.Wireframe.Events.unbind('.single');

    variables.view_wrapper.removeClass('empty single').addClass('multi');
    variables.empty_wrapper.hide();
    variables.single_wrapper.empty().hide();
    variables.multi_wrapper.show();

    variables.loaded_item = null;

    // remove the form for multi actions if no multi actions exist
//    if(variables.multi_wrapper.find('table.objects_list_multi_actions tr').length == 0) {
//    	variables.multi_wrapper.find('form.objects_list_multi_actions_form').remove();
//      if (variables.view_wrapper.find('div.no_multi_actions').length == 0) {
//        variables.multi_wrapper.append('<div class="no_multi_actions">' + App.lang('There are no available actions to perform') + '</div>');
//      } // if
//    }//if
    
    trigger_event.apply(_this, ['multi']);

    cancel_single_loading.apply(_this);
  }; // load_multi
  
  
  /**
   * Init printing
   */
  var init_printing = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var print_button = $('<li class="object_list_option object_list_print_option" title="' + App.lang('Print') + '"><span class="object_list_option_button">' + App.lang('Print') + '</span></li>').appendTo(variables.objects_list_options);
    print_button.click(function () {
      if (print_button.is('.disabled')) {
        return false;
      } // if
      var print_url = App.extendUrl(variables.settings.print_url, {'group_by' : variables.active_grouping, 'filter_by' : variables.active_filters});
      App.Wireframe.Print.doPrint(print_url);
      return false;
    });   
  }; // init_printing
  
  /**
   * Add filter
   *
   * @param String property
   * @param mixed value,
   * @param Boolean skip_refresh
   */
  var add_filter = function(property, value, skip_refresh) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    // first we need to remove filter already set
//    remove_filter.apply(this, [property, true]);
    
    variables.active_filters[property] = value;

    if (!skip_refresh) {
      update_items_list.apply(_this, [false]);
    } // if
  }; // addFilter
  
  /**
   * Remove filter by field name
   *
   * @param String property
   * @param Boolean skip_refresh
   */
  var remove_filter = function (property, skip_refresh) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    delete variables.active_filters[property];
    
    if (!skip_refresh) {
      update_items_list.apply(_this, [false]);
    } // if
  }; // removeFilter
  
  /**
   * Init filtering
   */
  var init_filtering = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    //init breadcrumbs
    init_breadcrumbs.apply(_this);
    
    variables.active_filters = {};    
    $.each(variables.settings.filtering, function (filter_index, filter) {
    	
      if ($.inArray(filter.property, variables.properties) == -1) {
        variables.properties.push(filter.property);
      } // if

      var default_filtering;
      var group_dropdown = '<li class="object_list_option with_dropdown" title="' + App.lang('Filter By :property_name', {property_name : filter.label}) + '"><span class="object_list_option_button">' + App.lang('Filter By :property_name', {property_name : filter.label}) + '</span><div class="object_list_option_dropdown"><p class="object_list_option_label">' + App.lang('Filter By :property_name', {property_name : filter.label}) + '</p><ul>';
      
      $.each(filter.values, function (value_index, filter_value) {
        group_dropdown += '<li><label><input type="radio" name="' + filter.property + '" value="' + filter_value.value + '" class="filter" icon="' + filter_value.icon + '" breadcrumbs="' + filter_value.breadcrumbs + '" />' + filter_value.label + '</label></li>';
        if (filter_value['default'] == true) {
          default_filtering = filter_value.value;
          add_filter.apply(_this, [filter.property, filter_value.value, true]);
          breadcrumbs_update_last.apply(_this,[filter_value['breadcrumbs']]);
        } // if
        
      });    
      group_dropdown += '</ul></div></li>';
      group_dropdown = $(group_dropdown);

      // save filtering
      var saved_filtering = get_cookie.apply(_this, ['filtering_' + filter.property]);
      var current_filtering = saved_filtering === false ? default_filtering : saved_filtering;
      
      group_dropdown.find('input[type=radio][value=' + current_filtering + ']').attr('checked', true);
      add_filter.apply(_this, [filter.property, current_filtering, true]);
      
      var checked_radio = group_dropdown.find('input[type=radio]:checked:last');
      if (checked_radio.length) {
        checked_radio.parents('.object_list_option.with_dropdown').find('.object_list_option_button').css('background-image', 'url(' + checked_radio.attr('icon') + ')');
        breadcrumbs_update_last.apply(_this,[checked_radio.attr('breadcrumbs')]);
      } // if
     
      variables.objects_list_options.append(group_dropdown);
      
    });
  }; // init_filtering
  
  /**
   * Add additional buttons if they exists
   */
  var init_additional_buttons = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (variables.settings.additional_buttons) {
      $.each(variables.settings.additional_buttons, function (button_id, button) {
        
        var additional_button = $('<li class="object_list_option" id="' + button_id + '" title="' + button.title + '"><span class="object_list_option_button" >' + button.title + '</span></li>');
        var additional_button_span = additional_button.find('span:first').css('background-image', 'url(' + button.icon + ')');
        variables.objects_list_options.append(additional_button);
        
        if (button.onclick) {
          additional_button.click(function (event) {
            button.onclick.apply(this);
            event.stopPropagation();
            return false;
          });
        } // if
        
      });
    }; // if
  }; // init_additional_buttons
  
  /**
   * Init breadcrumbs
   */
  var init_breadcrumbs = function() {
	  var _this = this;
	  var $this = $(this);
	  var variables = _variables($this); 
	  
	  variables.breadcrumbs = {}; 
	  
	  variables.breadcrumbs.initial = $("ul#breadcrumbs").clone();
	  variables.breadcrumbs.default_value = $("ul#breadcrumbs li:last").text();
  };//init_breadcrumbs
  
  /**
   * Add text to the end of breadcrumbs
   * 
   */
  var breadcrumbs_update_last = function(value) {
	  var _this = this;
	  var $this = $(this);
	  var variables = _variables($this);
	  if(value == 'undefined') {
		  value = variables.breadcrumbs.default_value;
	  }//if
	  variables.breadcrumbs.initial.find('li:last').html("<span class='current'>" + value + "</span>");
	  $("ul#breadcrumbs").html(variables.breadcrumbs.initial.clone().children());
  };//breadcrumbs_update_last
  
  
  /**
   * Set objects list cookie
   * 
   * @param String type
   * @param String value
   */
  var set_cookie = function (type, value) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    if (!type) {
      return true;
    } // if
    
    var cookie_content = $.trim($.cookie('ac_objects_lists'));
    if (!cookie_content) {
      cookie_content = {};
    } else {
      cookie_content = $.parseJSON(cookie_content);
    } // if
    
    if (!cookie_content[variables.settings.objects_type]) {
      cookie_content[variables.settings.objects_type] = {};
    } // if
    
    cookie_content[variables.settings.objects_type][type] = value;
    
    $.cookie('ac_objects_lists', $.toJSON(cookie_content), {expires : 90});
  }; // set_cookie
  
  /**
   * Get cookie value for this objects list
   * 
   * @param String type
   * @return String
   */
  var get_cookie = function (type) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    if (!type) {
      return false;
    } // if
    
    var cookie_content = $.trim($.cookie('ac_objects_lists'));
    if (!cookie_content) {
      return false;
    } // if
    
    cookie_content = $.parseJSON(cookie_content);
    if (cookie_content[variables.settings.objects_type] && ((cookie_content[variables.settings.objects_type][type] !== undefined) && (cookie_content[variables.settings.objects_type][type] !== false))) {
      return cookie_content[variables.settings.objects_type][type];
    } // if
    return false;
  }; // get_cookie
  
  /**
   * This will update initialy set grouping with fresh mappings
   *
   * @param Object grouping
   * @param Object new_mapping
   */
  var update_grouping = function(grouping, new_mapping) {
    var _this = this;
    
    grouping.map = new_mapping;
    update_items_list.apply(_this);
  }; // update_grouping
  
  /**
   * Init grouping
   */
  var init_grouping = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
        
    var default_grouping;
    var group_dropdown = '<li class="object_list_option with_dropdown object_list_grouping" title="' + App.lang('Group By') + '"><span class="object_list_option_button">' + App.lang('Group By') + '</span><div class="object_list_option_dropdown"><p class="object_list_option_label">' + App.lang('Group By') + '</p><ul>';
    $.each(variables.settings.grouping, function (index, current) {
      
      variables.properties.push(current.property);
      group_dropdown += '<li' + (current.map === null ? ' class="disabled" ' : '') + '><label><input type="radio" name="grouping" value="' + current.property + '" icon="' + current.icon + '"' + (current.map === null ? ' disabled="disabled" ' : '') + ' />' + current.label + '</label></li>';
            
      // listen for this event to update current_mapping
      if (current.listen) {
        App.Wireframe.Events.bind(current.listen + '.content', function (event, new_mapping) {
          update_grouping.apply(_this, [current, new_mapping]);
        });
      } // if
      
      if (current['default'] == true) {
        default_grouping = current.property;
        set_grouping.apply(_this, [current.property, true]);
      } // if
    });    
    group_dropdown += '</ul></div></li>';
    group_dropdown = $(group_dropdown);

    // save grouping
    var saved_grouping = get_cookie.apply(_this, ['grouping']);
    var current_grouping = saved_grouping === false ? default_grouping : saved_grouping;
        
    // we need to check if maybe that map is not set
    $.each(variables.settings.grouping, function (index, current) {    
      if ((current.property == current_grouping) && (!current.map)) {
        current_grouping = '';
      } // if
    });
    
    group_dropdown.find('input[type=radio][value=' + current_grouping + ']').attr('checked', true);
    set_grouping.apply(_this, [current_grouping, true]);
    
    var checked_radio = group_dropdown.find('input[type=radio]:checked:last');
    if (checked_radio.length) {
      checked_radio.parents('.object_list_option.with_dropdown').find('.object_list_option_button').css('background-image', 'url(' + checked_radio.attr('icon') + ')');
    } // if
    
    if (variables.settings.grouping.length < 2) {
      group_dropdown.hide();
    } // if
    
    variables.objects_list_options.append(group_dropdown);
  }; // init_grouping
  
  /**
   * Set's the current grouping mode
   *
   * @param String grouping
   */
  var set_grouping = function (grouping, skip_refresh) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    variables.active_grouping = grouping;
    if (!skip_refresh) {
      update_items_list.apply(_this, [false]);
    } // if
  }; // set_grouping
  
  /**
   * Remove active grouping
   */
  var remove_grouping = function (skip_refresh) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    variables.active_grouping = null;
    if (!skip_refresh) {
      update_items_list.apply(_this);
    } // if
  }; // remove_grouping
  
  /**
   * Initialise reordering function of objects list
   */
  var init_reordering = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var reordering_button = $('<li class="object_list_option object_list_reorder_option" title="' + App.lang('Reorder') + '"><span class="object_list_option_button object_list_reorder_button">' + App.lang('Reorder') + '</span></li>').click(function () {
      if (reordering_button.is('.opened')) {
        exit_reorder_mode.apply(_this);
      } else {
        enter_reorder_mode.apply(_this);
      } // if
    });
    
    variables.objects_list_options.append(reordering_button);
  }; // init_reordering
  
  /**
   * Enter reordering mode
   */
  var enter_reorder_mode = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);
    
    variables.objects_list_search_filter.attr('disabled', true).addClass('disabled');
    variables.objects_list_options.find('li:not(.object_list_reorder_option)').addClass('disabled');
    
    $this.addClass('reorder_mode');
    variables.objects_list_body.empty().append('<table cellspacing="0">' + storage.items.html() + '</table>');
    
    variables.objects_list_body.find('tr').each(function () {
      this.innerHTML = '<td class="drag_handle"><img src="' + App.Wireframe.Utils.imageUrl('layout/bits/handle-drag.png', 'environment') + '" /></td>' + this.innerHTML;
    });
    
    var save_timeout;
    
    variables.objects_list_body.find('table:first').sortable({
      'items'           : 'tr',
      'axis'            : 'y',
      'forceHelperSize' : true,
      'helper'          : function(e, tr) {
        var $originals = tr.children();
        var $helper = tr.clone();
        $helper.children().each(function(index) {
          $(this).width($originals.eq(index).width());
        });
        return $helper;
      },
      'update'          : function (event, ui) {
        if (save_timeout) {
          clearTimeout(save_timeout);
        } // if
        
        save_timeout = setTimeout(function () {
          save_ordering.apply(_this);          
        }, 1000);
      }
    });
  }; // enter_reorder_mode
  
  /**
   * Save the order of items
   */
  var save_ordering = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);
    var container = storage.items;
    var ordered = new Array();
    
    container.empty();
    variables.objects_list_body.find('tr').each(function () {
      var clone = $(this).clone();
      ordered.push(clone.attr('item_id'));
      clone.find('td.drag_handle').remove();
      clone.appendTo(container);
    });
    
    $.ajax({
      'url'     : App.extendUrl(variables.settings.reorder_url, {async : 1}),
      'data'    : {
        'object_ids'  : ordered,
        'submitted'   : 'submitted'
      },
      'type'    : 'POST',
      'success' : function (response) {
      },
      'error'   : function (response) { }
    });
  }; // save_ordering
  
  /**
   * Exit from reordering mode
   */
  var exit_reorder_mode = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    variables.objects_list_search_filter.attr('disabled', false).removeClass('disabled');
    variables.objects_list_options.find('li:not(.object_list_reorder_option)').removeClass('disabled');
    
    $this.removeClass('reorder_mode');
    update_items_list.apply(this);
  }; // exit_reorder_mode
  
  /**
   * Initialize options and search bar
   */
  var init_options_and_search = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    // on hoverout hide opened dropdowns
    var dropdowns = variables.objects_list_options.find('.object_list_option.with_dropdown').each(function () {
      var option_dropdown = $(this);
      option_dropdown.delayedHover(null, function () {
        option_dropdown.removeClass('opened');
      }, 200);
    });
    
    // toggle opened dropdowns
    variables.objects_list_options.click(function (event) {
      var target = $(event.target);
      var target_parent = target.parent();
      
      if (target.is('span.object_list_option_button')) {
        if (target_parent.is('.disabled')) {
          return false;
        } // if
        
        target_parent.toggleClass('opened');
        return false;        
      } // if
      
      if (target.is('input[type=radio]')) {
        if (target.attr('icon')) {
          var icon_parent = target.parents('.object_list_option.with_dropdown').find('.object_list_option_button');
          icon_parent.css('background-image', 'url(' + target.attr('icon') + ')');
        } // if
          
        // groupping
        if (target.is('input[name=grouping]')) {
          if (target.val()) {
            set_grouping.apply(_this, [target.val()]);
            set_cookie.apply($this, ['grouping', target.val()]);
          } else {
            remove_grouping.apply(_this);
            set_cookie.apply($this, ['grouping', '']);
          } // if
        } else if (target.is('.filter')) {
          if (target.val()) {
            add_filter.apply(_this, [target.attr('name'), target.val()]);
            set_cookie.apply($this, ['filtering_' + target.attr('name'), target.val()]);
          } else {
            remove_filter.apply(_this, [target.attr('name')]);
            set_cookie.apply($this, ['filtering_' + target.attr('name'), '']);
          } // if
          breadcrumbs_update_last.apply(_this,[target.attr('breadcrumbs')]);
        } // if
        
        // close dropdown
        target.parents('.object_list_option.with_dropdown.opened').removeClass('opened');
      } // if      
    });
    
    /**
     * Resize search field
     */
    var resize_search_filter = function () {
      variables.objects_list_search_filter_parent.css('width', variables.objects_list_head.width() - variables.objects_list_options_parent.width());
    }; // resize_search
    resize_search_filter();
    App.Wireframe.Events.bind('window_resize', resize_search_filter);
    
    var up_down_timeout;
    
    /**
     * Handle up/down navigation with up/down keys
     * 
     * @param Object event
     * @param jQuery row
     */
    var up_down_item_nav = function (event) {
  	  var _this = this;
  	  var $this = $(this);
  	  var variables = _variables($this);
  	  var row = null;
	  
  	  if (event.keyCode == 38) { //up arrow
  		if (variables.objects_list_body.find('div.object_list_group_head.clickable.selected').length == 1 ) { //group selected
  		  var object_list_group = variables.objects_list_body.find('div.object_list_group_head.clickable.selected').parent();
  		  do {
  			var object_list_group = object_list_group.prev();  
  			row = object_list_group.find('tr.objects_list_item:last');
  			if (row.length == 0) {
  			  row = object_list_group.find('div.object_list_group_head.clickable');
  			} //if
  		  } while (row.length == 0 && object_list_group.length > 0); //while
  		  
  		} else { // object(s) selected
	  	  row = variables.objects_list_body.find('tr.objects_list_item.selected:first').prev();
	  	  current_group_not_visible = variables.objects_list_body.find('tr.objects_list_item.selected:first').parents(':eq(2)').css('display') === 'none';
	  	  if (row.length == 0 || current_group_not_visible) {
	  	    var object_list_group = variables.objects_list_body.find('tr.objects_list_item.selected:first').parents(':eq(3)');
	  	    if (object_list_group.find('div.object_list_group_head.clickable').length == 1) { //group clickable
	  	      row = object_list_group.find('div.object_list_group_head.clickable');
	  	    } else {
	  	      do {
	    	    var object_list_group = object_list_group.prev();
	    	    row = object_list_group.find('tr.objects_list_item:last');
	    	    var group_not_visible = object_list_group.find('.object_list_group_body').css('display') === 'none';
	  	      } while ((row.length == 0 || group_not_visible) && object_list_group.length > 0); //while
	  	    } //if
	  	  } //if
  		} //if
  	    
  	  } else if (event.keyCode == 40) { //down arrow
	    if (variables.objects_list_body.find('tr.objects_list_item.selected').length == 0 && variables.objects_list_body.find('div.object_list_group_head.clickable.selected').length == 0) { //none selected
    	  if (variables.objects_list_body.find('div.object_list_group_head.clickable:first').length == 1) {
    		row = variables.objects_list_body.find('div.object_list_group_head.clickable:first');
    	  } else {
	    	row = variables.objects_list_body.find('tr.objects_list_item:first');
    	  } //if
    	} else if (variables.objects_list_body.find('div.object_list_group_head.clickable.selected').length == 1) { //group selected
    	  var object_list_group = variables.objects_list_body.find('div.object_list_group_head.clickable.selected').parent();
    		do {
    		  row = object_list_group.find('tr.objects_list_item:first');  	
    		  if (row.length == 0) {
    			var object_list_group = object_list_group.next();  
        		row = object_list_group.find('div.object_list_group_head.clickable');
    		  } //if
    		} while (row.length == 0 && object_list_group.length > 0); //while	
    	} else { // object(s) selected
    	  row = variables.objects_list_body.find('tr.objects_list_item.selected:last').next();
    	  current_group_not_visible = variables.objects_list_body.find('tr.objects_list_item.selected:first').parents(':eq(2)').css('display') === 'none';
    	  if (row.length == 0 || current_group_not_visible) {
    	    var object_list_group = variables.objects_list_body.find('tr.objects_list_item.selected:last').parents(':eq(3)');
    	    if (object_list_group.next().find('div.object_list_group_head.clickable').length == 1) { //group clickable
    	      row = object_list_group.next().find('div.object_list_group_head.clickable');
    	    } else {
      	      do {
      	        var object_list_group = object_list_group.next();
      	        row = object_list_group.find('tr.objects_list_item:first');
      	        var group_not_visible = object_list_group.find('.object_list_group_body').css('display') === 'none';
      	      } while ((row.length == 0 || group_not_visible) && object_list_group.length > 0); //while
    	    } //if
    	  } //if
    	} //if
  	  } //if
  	    	  
  	  if (row && row.length > 0) {
        if (up_down_timeout) {
          clearInterval(up_down_timeout);
        } //if
  	    
  	    variables.objects_list_body.find('tr.objects_list_item.selected').removeClass('selected');
  	    variables.objects_list_body.find('div.object_list_group_head.clickable.selected').removeClass('selected');
  	    row.addClass('selected');
  	    mark_single_row.apply(_this,[row]);
  	    up_down_timeout = setTimeout(function () {
          trigger_event.apply(_this, ['item_selected', [row]]);
          load_single.apply(_this, [row]);
  	    }, 500);
  	  }//if
    }; // up_down_item_nav
    
    var search_timeout;
    
    /**
     * Do the search
     */
    var do_search = function () {
      var query = $.trim(variables.objects_list_search_filter.val());
      if (query) {
        variables.objects_list_cancel_search_filter.show();
      } else {
        variables.objects_list_cancel_search_filter.hide();
      } // if

        // if we already have the same search result, don't perform the search
      if (query != variables.active_search_query) {
        variables.active_search_query = query;
        update_items_list.apply(_this, [false]);
      } // if
      if (search_timeout) {
    	clearInterval(search_timeout);
      } //if
    }; // do_search
    
    // handle search input events
    variables.objects_list_search_filter.bind('click keyup', function (event) {
      if (event.type == 'keyup' && event.keyCode == 27) {
        variables.objects_list_cancel_search_filter.hide();
        variables.objects_list_search_filter.blur().attr('value', '').focus();
        do_search();
      } else if (event.type == 'keyup' && event.keyCode == 13) {
        do_search();
      } else if (event.type == 'keyup' && (event.keyCode == 40 || event.keyCode == 38)) {
        up_down_item_nav.apply(_this, [event]);
      } else {
        if (search_timeout) { 
          clearTimeout(search_timeout);
        } //if
        search_timeout = setTimeout(do_search, 300);
      } // if
    });
    
    variables.objects_list_cancel_search_filter.click(function () {
      variables.objects_list_cancel_search_filter.hide();
      variables.objects_list_search_filter.blur().attr('value', '').focus();
      do_search();      
      return false;
    });
    
    variables.objects_list_cancel_search_filter.hide();
    variables.objects_list_search_filter.focus();
  }; // init_options_and_search
    
  /**
   * Initialize multi form behavior
   */
  var init_multi_form = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var multi_form_button = variables.multi_form.find('.button_holder button[type=submit]');
    variables.multi_form.click(function (event) {
      var target = $(event.target);
      var target_cell = target.parents('td:first');
      if (target.is('input[type=checkbox]') && target_cell.is('.checkbox')) {
        var hidden_fields = target_cell.next('td.new_value').find('*');
        if (target.is(':checked')) {
          multi_form_button.attr('disabled', false);
          hidden_fields.show();
        } else {
          if (!variables.multi_form.find('td.checkbox input[type=checkbox]:checked').length) {
            multi_form_button.attr('disabled', true);
          } // if
          hidden_fields.hide();
        } // if
      } // if
    });
    
    variables.multi_form.submit(function () {
      data = {
        selected_item_ids : [],
        actions : [],
        variables : {},
        objects_type : variables.settings.objects_type,
        submitted : 'submitted'
      };
      
      variables.multi_form.find('td.checkbox input:checked').each(function () {
        var checkbox = $(this);
        var row = checkbox.parents('tr:first');
        data.actions.push(checkbox.attr('value'));

        row.find('td.new_value input:visible, td.new_value select:visible, td.new_value textarea:visible').each(function () {
          var field = $(this);
          if (field.is('[type=checkbox], [type=radio]')) {
            if (field.is(':checked')) {
              data.variables[field.attr('name')] = field.attr('value');            
            } // if
          } else {
            data.variables[field.attr('name')] = field.attr('value');
          } // if
        });        
      });
      
      variables.objects_list_body.find('tr.selected').each(function () {
        data.selected_item_ids.push(this.getAttribute('item_id'));
      });
      
      multi_form_button.attr('disabled', true);
      variables.multi_wrapper.find('form').block();
      $.ajax({
        'type' : 'post',
        'url' : App.extendUrl(variables.settings.multi_url, {async : 1}),
        'data' : data,
        'success' : function (response) {
          $.each(response, function (index, item) {
            update_item.apply(_this, [item, true, true]);
          });
          update_items_list.apply(_this);
          
          if(response.length == 1) {
            App.Wireframe.Flash.success('One item updated'); 
          } else {
            App.Wireframe.Flash.success(':num items updated', {
              'num' : response.length 
            });
          } // if

          App.Wireframe.Events.trigger(variables.settings.objects_type + '_mass_updated', [null]);
        },
        'error' : function (response) {
          App.Wireframe.Flash.error(App.lang('Failed to update multiple ') + App.lang(variables.settings.objects_type));
        },
        'complete' : function () {
          multi_form_button.attr('disabled', false);
          variables.multi_wrapper.find('form').unblock();          
        }
      });
      return false;
    });
    
    variables.multi_wrapper.find('form.separate_actions button').click(function () {
      var button = $(this);
      
      // confirmation
      if (button.attr('confirm') && !confirm(button.attr('confirm'))) {
        return false;
      } // if
      
      var form = button.parents('form:first');
      
      data = {
          'selected_item_ids' : [],
          'actions' : [button.attr('value')],
          'variables' : {},
          'objects_type' : variables.settings.objects_type,
          'submitted' : 'submitted'
      };
      
      variables.objects_list_body.find('tr.selected').each(function () {
        data.selected_item_ids.push(this.getAttribute('item_id'));
      });
      
      variables.multi_wrapper.find('form').block();
      $.ajax ({
        'url'   : App.extendUrl(variables.settings.multi_url, {async : 1}),
        'type' : 'post',
        'data'  : data,
        'success' : function (response) {
          if (button.attr('after') == 'delete') {
            $.each(response, function (index, item) {
              delete_item.apply(_this, [item, true, true]);
            });
          } else {
            $.each(response, function (index, item) {
              update_item.apply(_this, [item, true, true]);
            });
          } // if
          
          update_items_list.apply(_this);
          
          if(response.length == 1) {
            App.Wireframe.Flash.success('One item updated'); 
          } else {
            App.Wireframe.Flash.success(':num items updated', {
              'num' : response.length 
            });
          } // if

          App.Wireframe.Events.trigger(variables.settings.objects_type + '_mass_updated', [null]);
        },
        'error' : function (response) {
          App.Wireframe.Flash.error(App.lang('Failed to update multiple ') + App.lang(variables.settings.objects_type));
        },
        'complete' : function () {
          variables.multi_wrapper.find('form').unblock();
        }
      });
      
      return false;
    });
  }; // init_multi_form
  
  /**
   * Draws the initial wireframe
   */
  var draw_initial_wireframe = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    $this.attr('list_id', variables.settings.id);
    $this.addClass('objects_list_wrapper');
    
    variables.objects_list = $('' + 
    '<div class="objects_list">' +
      '<div class="objects_list_head">' + 
        '<div class="object_list_filter"><input type="text" /><img src="' + App.Wireframe.Utils.imageUrl('objects-list/cancel-search.png', 'environment') + '" alt="x" /></div>' +
        '<div class="object_list_options"><ul></ul></div>' +
      '</div>' +
      '<div class="objects_list_body"></div>' +
    '</div>');
    
    $this.prepend(variables.objects_list);
    variables.objects_list_head = variables.objects_list.find('.objects_list_head:first');    
    variables.objects_list_body = variables.objects_list.find('.objects_list_body:first');
    variables.objects_list_search_filter = variables.objects_list.find('.object_list_filter:first input:first');
    variables.objects_list_cancel_search_filter = variables.objects_list.find('.object_list_filter:first img:first');
    variables.objects_list_search_filter_parent = variables.objects_list_search_filter.parents('.object_list_filter:first');
    variables.objects_list_options = variables.objects_list.find('.object_list_options ul:first');
    variables.objects_list_options_parent = variables.objects_list_options.parents('div.object_list_options:first');
    
    variables.view_wrapper = $('' +
    '<div class="objects_list_details">' +
      '<div class="objects_list_details_empty_wrapper"></div>' +
      '<div class="objects_list_details_single_wrapper"></div>' +
      '<div class="objects_list_details_multi_wrapper">' +
        '<div class="objects_list_title"></div>' +
      '</div>' +
    '</div>');

    $this.append(variables.view_wrapper);
    variables.empty_wrapper = variables.view_wrapper.find('.objects_list_details_empty_wrapper').append($this.find('.empty_content').detach());
    variables.single_wrapper = variables.view_wrapper.find('.objects_list_details_single_wrapper');
    variables.multi_wrapper = variables.view_wrapper.find('.objects_list_details_multi_wrapper');
    variables.multi_title = variables.multi_wrapper.find('.objects_list_title');
    
    var multi_actions = variables.settings.multi_actions;
    
    
    if (multi_actions && !$.isEmptyObject(multi_actions)) {
      var multiple = multi_actions['multiple'];
      if (multiple && !$.isEmptyObject(multiple)) {
        var multiple_wrapper = $('' + 
          '<div class="group">' + 
            '<form class="objects_list_multi_actions_form multiple_actions" method="post" action="' + variables.settings.multi_url  + '">' +
              '<table class="objects_list_multi_actions">' + 
              '</table>' + 
              '<input type="hidden" style="display: none" value="submitted" name="submitted">' +
              '<div class="button_holder"><button disabled="disabled" type="submit" class="default"><span><span>' + App.lang('Update Selected Items') + '</span></span></button></div>' +
            '</form>' +
          '</div>'
        ).appendTo(variables.multi_wrapper);
        
        var multiple_wrapper_table = multiple_wrapper.find('table:first');
        $.each(multiple, function (action_name, data) {
          multiple_wrapper_table.append('<tr>' +
            '<td class="checkbox"><label><input type="checkbox" value="' + action_name + '">' + data['title']  + '</label></td>' +
            '<td class="new_value">' + data['controls'] + '</td>' +
          '</tr>');
        });        
      } // if
      
      var separate = multi_actions['separate'];
      if (separate && !$.isEmptyObject(separate)) {
        var separate_wrapper = $('<div class="group">' + 
            '<form class="objects_list_multi_actions_form separate_actions" method="post" action="' + variables.settings.multi_url  + '">' +
              '<input type="hidden" style="display: none" value="submitted" name="submitted">' +
            '</form>' +
        '</div>').appendTo(variables.multi_wrapper);
          
        var separate_count = 0;
        $.each(separate, function (action_name, data) {
          $('<div class="separate_action">' +
              '<img src="' + data['icon'] + '" /><br />' +
              '<button type="submit" name="action" value="' + action_name + '" after="' + data['after'] + '" confirm="' + (data['confirm'] ? data['confirm'] : '') + '" objects="' + data['objects'] + '"><span><span>' + data['title'] + '</span></span></button>' +
          '</div>').appendTo(separate_wrapper.find('form:first'));
          separate_count++;
        });
        
        separate_wrapper.find('div.separate_action').css('width', Math.floor(100 / separate_count) + '%');
      } // if
    } else {
      variables.multi_wrapper.append('<div class="no_multi_actions">' + App.lang('There are no available actions to perform') + '</div>');
    } // if
    variables.multi_form = variables.multi_wrapper.find('form.multiple_actions:first');
    variables.empty_page_events = App.Wireframe.Events.backup('.single');
        
  	init_multi_form.apply(_this);
  }; // draw_initial_wireframe
  
  /**
   * function which renders does the default layout
   * 
   * @param Object item (item has to have id, and name as minimum)
   * @return String
   */
  var default_render_item = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    return '<td>' + real_field_value(item, 'name', variables.settings.field_map).clean() + '</td>'; 
  }; // default_render_item
  
  /**
   * Default way to render a group content
   * 
   * @param Object group
   */
  var default_render_group = function (group_id, group) {
    return group;
  }; // default_render_group
  
  /**
   * Get search index for specific item
   *
   * @param Object item
   * @return string
   */
  var get_item_search_index = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    var search_index = '';
    
    if (variables.settings.search_index && typeof variables.settings.search_index == 'function') {
      search_index = variables.settings.search_index.apply(_this, [item]);
    } else {
      search_index = real_field_value(item, 'name', variables.settings.field_map).toLowerCase().clean();
    } // if

    return ' ' + $.trim(search_index.replace(/\"/g, '')) + ' ';
  }; // get_item_search_index
  
  /**
   * Get search index for specific group
   *
   * @param Object group
   * @return string
   */
  var get_group_search_index = function (group) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (typeof(group) == 'string') {
      return group;
    } else {
      return group.name;
    } // if
  }; // get_group_search_index
  
  /**
   * Get the real field value
   *
   * @param field_name
   * @return mixed
   */
  var real_field_value = function (item, field, map) {
    if (map && map[field]) {
      var traverse = map[field].split('.');
      
      var field_value = item;
      $.each(traverse, function (index, subfield_name) {
        if (subfield_name) {
          field_value = field_value[subfield_name];
          if (field_value === '' || field_value === null || field_value === undefined) {
            field_value = '';
            return false;
          } // if
        } // if
      });
      return field_value;
    } else {
      var field_value = item[field];
      if (field_value === '' || field_value === null || field_value === undefined) {
        field_value = '';
      } // if
      return field_value;
    } // mix
  }; // real_field_value
  
  /**
   *  Do the post rendering add required properties by grouping and filtering
   *
   *  @param string pre_rendere
   *  @param Object item
   *  @return string
   */
  var render_item = function (item) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    var pre_rendered = variables.settings.render_item.apply(_this, [item]);
    var row = '<tr class="objects_list_item" item_id="' + item.id + '" permalink="' + (real_field_value(item, 'permalink', variables.settings.field_map) ? real_field_value(item, 'permalink', variables.settings.field_map) : '') + '"';    
    if (variables.properties && variables.properties.length) {
      $.each(variables.properties, function (index, property) {
    	
        var property_value = real_field_value(item, property, variables.settings.field_map);
        
        if (!(property_value === '' || property_value === null || property_value === undefined)) {
          row += ' '+property+'="' + property_value + '" ';
        } // if
      });
      row += '_search_index="' + get_item_search_index.apply(_this, [item]).toLowerCase() + '"';
    } // if
    
    row+= '>' + pre_rendered;
    
    if (variables.settings.show_goto_arrow) {
      row+= '<td class="goto"><a href="' + (item.goto_url ? item.goto_url : item.permalink) + '"><img src="' + App.Wireframe.Utils.imageUrl('layout/projects/go.png', 'system') + '" alt="&gt;" /></a></td>';
    } // if
    
    row+= '</tr>';
    return row;
  }; // post_render
  
  /**
   * Do the group rendering
   */
  var render_group = function (group_id, group_item, active_grouping) {
    var _this = this;
    var rendered = '<div class="object_list_group"><div class="object_list_group_head ' + (active_grouping.clickable ? 'clickable' : '') + '" '+ (active_grouping.clickable ? 'permalink="' + group_item['permalink'] + '"' : '') +' _search_index=" ' + get_group_search_index.apply(_this, [group_item]).toLowerCase() + ' " ';
    if (active_grouping.filters && typeof(active_grouping.filters) == 'object' && active_grouping.filters.length) {
      $.each(active_grouping.filters, function (index, filter) {
        rendered += filter + '="' + group_item[filter] + '"';
      });
    } // if
    rendered += ' group_id="' + group_id + '" grouping_property="' + active_grouping.property + '" item_id="' + group_id + '">';
    
    if (active_grouping.render_group && typeof(active_grouping.render_group) == 'function') {
      rendered += active_grouping.render_group.apply(_this, [group_id, group_item]);
    } else {
      rendered += default_render_group.apply(_this, [group_id, group_item]);
    } // if
    rendered += '</div><div class="object_list_group_body"><table cellspacing="0"><tbody></tbody></table></div></div>';
    
    return rendered;
  }; // render_group
  
  /**
   * Refresh the objects list
   */
  var update_items_list = function (preserve_collapsed_groups) {
    // in some cases we don't want groups to remain collapsed (like when grouping is changed)
    preserve_collapsed_groups = typeof preserve_collapsed_groups !== 'undefined' ? preserve_collapsed_groups : true;
    
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    var storage = _storage($this);
    
    var data = storage.items;

    // save ids of collapsed groups to apply the collapsed state after rebuilding the list
    var object_list_group_bodies = $('div.objects_list_wrapper').find('div.object_list_group_body');
    var collapsed_group_ids = [];
    if (object_list_group_bodies) {
      object_list_group_bodies.each(function() {
        var objects_list_group = $(this);
        if (objects_list_group.is(':hidden')){
          collapsed_group_ids.push(objects_list_group.parent().find('div.object_list_group_head ').attr('group_id'));
        } // if
      });
    } // if
    
    // apply active filters
    if (variables.active_filters) {
      var filter_query = 'tr';      
      $.each(variables.active_filters, function (property, value) {
    	if (!(value === '' || value === null || value === undefined)) {
          filter_query += '[' + property + '="' + value + '"]';
        } // if
      });
      var filtered_data = $('<div></div>').append(data.find(filter_query).clone());
    } else {
      var filtered_data = data.clone();
    } // if

    var search_query = '';
    if (variables.active_search_query) {
      var active_search_query = $.trim(variables.active_search_query.toLowerCase().replace(/\"/g, ''));

      $.each(active_search_query.split(' '), function (index, search_fragment) {
        if (search_fragment) {
          search_query += '[_search_index*="' + search_fragment + '"]';
        } // if
      });

      var filtered_data = $('<div></div>').append(filtered_data.find('tr' + search_query));
      variables.objects_list.addClass('search_mode');
    } else {
      variables.objects_list.removeClass('search_mode');
    } // if
    
    // group items if necessary
    var updated_list = $('<div></div>');
    //kad nema kat ovde ne ulazi
    if (variables.active_grouping) {
      var active_grouping;
      
      $.each(variables.settings.grouping, function (index, grouping) {
        if (grouping.property == variables.active_grouping) {
          active_grouping = grouping;
          return false;
        } // if
      });
         
      // is map is non existent then don't group the items
      if (active_grouping.map) {      
        var inverted_search = 'tr';
        $.each(active_grouping.map, function (property_value, group_item) {
          var current_group = $(render_group.apply(_this, [property_value, group_item, active_grouping]));
          current_group.find('tbody').append(filtered_data.find('tr[' + active_grouping.property + '="' + property_value + '"]'));
          inverted_search+= '[' + active_grouping.property + '!="' + property_value + '"]';
          updated_list.append(current_group);
          delete current_group;
        });
      
        // inverted search is all items that are not in current grouping map
        var data_found = filtered_data.find(inverted_search);

        // show the 'uncategorized' group only if actually there is something to put there
        if (data_found.length > 0) {
          var uncategorized_label = active_grouping.uncategorized_label ? active_grouping.uncategorized_label : App.lang('Uncategorized');
          var current_group = $('<div class="object_list_group"><div class="object_list_group_head">' + uncategorized_label + '</div><div class="object_list_group_body"><table cellspacing="0"><tbody></tbody></table></div></div>');
          current_group.find('tbody').append(data_found);
          updated_list.append(current_group);

          delete current_group;
        } // if
      } else {
        updated_list = $('<table cellspacing="0"></table>').append(filtered_data.html());        
      } // if
    } else {
      updated_list = $('<table cellspacing="0"></table>').append(filtered_data.html());
    } // if
    
    variables.objects_list_body.empty().append(updated_list).disableSelection();
        
    // if groups are also affected by the filter, perform filter query on them
    if (variables.active_filters && active_grouping && active_grouping.filters && typeof(active_grouping.filters) == 'object' && active_grouping.filters.length) {
      
      var group_query = '';
      $.each(active_grouping.filters, function (grouping_filter_index, grouping_filter) {
        $.each(variables.active_filters, function (active_filter, active_filter_value) {
          if ((active_filter == grouping_filter) && !(active_filter_value === '' || active_filter_value === null || active_filter_value === undefined)) {
            group_query += '[' + active_filter + '=' + active_filter_value + ']';
          } // if            
        });
      });
            
      if (group_query) {
        variables.objects_list_body.find('div.object_list_group div.object_list_group_head:visible:not(' + group_query + ')').each(function () {
          $(this).parent().hide();
        });        
      } // if
    } // if
    
    // if grouping is active and grouping is not set to show empty groups, hide them
    if (variables.active_grouping && (!active_grouping.show_empty || (active_grouping.searchable && variables.active_search_query))) {
      variables.objects_list_body.find('div.object_list_group:not(:has(tr.objects_list_item)) div.object_list_group_head').hide();
      variables.objects_list_body.find('div.object_list_group:not(:has(tr.objects_list_item)) div.object_list_group_body').hide();
    } // if
    
    // if groups are searchable, show them in results
    if (active_grouping && active_grouping.searchable && variables.active_search_query) {
      variables.objects_list_body.find('div.object_list_group:visible div.object_list_group_head' + search_query).show();
    } // if

    restore_selection.apply(_this);

    // count how many objects are in each group and show the number next to the group name
    if (variables.settings.show_items_count == true) {
      $('div.object_list_group').each(function() {
        var object_list_group = $(this);
        var list_group_items_count = object_list_group.find('table tbody').children().length;
        if (list_group_items_count > 0) {
          object_list_group.find('div.object_list_group_head').append('<span class="object_list_group_count">' + list_group_items_count + '</span>');
        } // if
      });
    } // if

    // group collapsing
    if (variables.settings.enable_group_collapsing == true) {
      $('div.object_list_group').each(function() {
        var object_list_group = $(this);
        var object_list_group_head = object_list_group.find('div.object_list_group_head');
        object_list_group_head.prepend('<span class="object_list_group_collapsable expanded">&nbsp;</span>');

        if (preserve_collapsed_groups && collapsed_group_ids.length) {
          if ($.inArray(object_list_group_head.attr('group_id'), collapsed_group_ids) > -1) {
            object_list_group.find('div.object_list_group_body').hide();
            object_list_group_head.find('span.object_list_group_collapsable').removeClass('expanded').addClass('collapsed');
          } // if
        } // if
        
        object_list_group_head.click(function() {
          object_list_group.find('div.object_list_group_body').slideToggle('fast');

          var collapsable_span = object_list_group_head.find('span.object_list_group_collapsable');
          if (collapsable_span.hasClass('expanded')) {
            collapsable_span.removeClass('expanded').addClass('collapsed');
          } else {
            collapsable_span.removeClass('collapsed').addClass('expanded');
          } //if
        });
      });
    } // if
    
    delete updated_list;
    delete data;
    delete filtered_data;
  }; // update_items_list
  
  /**
   * Trigger event if event exists
   *
   * @param String event
   * @return mixed
   */
  var trigger_event = function (event, event_arguments) {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (!$.isArray(event_arguments)) {
      if (event_arguments) {
        event_arguments = [].push(event_arguments);
      } else {
        event_arguments = [];
      } // if
    } // if
    
    if (variables.settings.events[event] && typeof variables.settings.events[event] == 'function') {
      return variables.settings.events[event].apply(_this, event_arguments);
    } // if
  }; // trigger_event
  
  var mark_single_row = function(item) {
  	var _this = this;
  	var $this = $(this);
  	var variables = _variables($this);  
	  
    var initial_offset = 6;
    var item_height = parseInt(item.height());
    var viewport_height = parseInt(variables.objects_list_body.height());
    var current_scroll = variables.objects_list_body.scrollTop();
    
    var item_offset_top = 0;
    if (item.offset()) {
      item_offset_top = item.offset().top;
    } // if
    
    var relative_offset = item_offset_top - variables.objects_list_body.offset().top + current_scroll;
    
    // be VERY carefull with this
    if (!((relative_offset > current_scroll) && (relative_offset < current_scroll + viewport_height))) {
      if (relative_offset > current_scroll + viewport_height) {
    	  variables.objects_list_body.scrollTop(relative_offset - viewport_height + item_height + initial_offset);
      } else if (relative_offset < current_scroll) {
    	  variables.objects_list_body.scrollTop(relative_offset - item_height + initial_offset);
      } // if
    } // if  
  }; //mark_single_row
  
  /**
   * Refresh the data for this object list
   */
  var refresh = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);
    
    if (!variables.settings.refresh_url) {
      return false;
    } // if
    
    var storage = _storage($this);
    var list_content = variables.objects_list_body.find(':first-child');

    list_content.hide();
    $this.addClass('refreshing');

    $.ajax({
      'url' : variables.settings.refresh_url,
      'success' : function (response) {
        if (!response) {
          response = [];
        } // if
        import_items.apply(_this, [response]);
        update_items_list.apply(_this, [false]);
      },
      'error' : function (response) {
        App.Wireframe.Flash.error('Failed to update item list');
      },
      'complete' : function (response) {
        list_content.show();
        $this.removeClass('refreshing');
      }
    });
  }; // refresh

  /**
   * Reload current item
   */
  var reload_current = function () {
    var _this = this;
    var $this = $(this);
    var variables = _variables($this);

    // find selected group or selected item
    var selected_item = variables.objects_list_body.find('tr.objects_list_item.selected');
    var selected_group = variables.objects_list_body.find('.object_list_group_head.clickable.selected:first');

    if (selected_item.length == 1) {
      do_load_single.apply(_this, [selected_item]);
      return true;
    } else if (selected_group.length) {
      do_load_single.apply(_this, [selected_group]);
      return true;
    } // if

    return false;
  }; // reload_current

  /**
   * check if item is selected
   *
   * @param id
   * @return boolean
   */
  var is_selected = function (id) {
    var $this = $(this);
    var variables = _variables($this);
    return variables.objects_list_body.find('tr.objects_list_item.selected[item_id="' + id + '"]').length > 0;
  }; // is_selected

  /**
   * Check if item is loaded
   *
   * @param mixed id
   * @param boolean is_group
   * @return boolean
   */
  var is_loaded = function (id, is_group) {
    var $this = $(this);
    var variables = _variables($this);
    return variables.loaded_item && variables.loaded_item['item_id'] == id && variables.loaded_item['is_group'] == is_group;
  };

  ////////////////////////////////////////////////////////// PLUGIN INITIAL DATA ///////////////////////////////////////////////////////////
  
  /**
   * Plugin name
   * 
   * @var String
   */
  var plugin_name = 'objectsList';
      
  /**
   * Default settings
   * 
   * @var Object
   */
  var settings = {
    items: null,
    show_goto_arrow : null,
    render_item : default_render_item,
    search_index: null,
    objects_type : null,
    group_by : null,
    show_items_count : true,
    enable_group_collapsing : true,
    multi_title : App.lang(':num Items Selected'),
    multi_url : '#',
    listen : {
      'add' : null,
      'edit' : null,
      'delete' : null
    },
    events : {
      empty : null,
      single : null,
      multi : null,
      single_loaded : null,
      item_selected : null,
      item_deselected : null
    }
  };
  
  ///////////////////////////////////////////////////////// PLUGIN FRAMEWORK STUFF /////////////////////////////////////////////////////////
  
  /**
   * Register jQuery plugin and handle public methods
   * 
   * @param mixed method
   */
  $.fn[plugin_name] = function(method) { 
    if (public_methods[method]) {
      return public_methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return public_methods.init.apply(this, arguments);
    } else {
      $.error('Method ' +  method + ' does not exist on jQuery.' + plugin_name);
    } // if
  };
  
  /**
   * handle variables
   * 
   * @param jQuery element
   * @param Object variables
   * @return mixed
   */
  var _variables = function (element) {
    var variables = element.data(plugin_name + 'Variables');
    if (variables) return variables;
    
    element.data(plugin_name + 'Variables', {});
    return element.data(plugin_name + 'Variables');
  }; // _variables
  
  /**
   * handle storage - itended for large objects that are not needed in every access to variables
   * 
   * @param jQuery element
   * @param Object variables
   * @return mixed
   */
  var _storage = function (element) {
    var storage = element.data(plugin_name + 'Storage');
    if (storage) return storage;
    
    // initialize data storage
    element.data(plugin_name + 'Storage', {});
    return element.data(plugin_name + 'Storage');
  }; // _storage
  
})(jQuery);