// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class Ext.ux.grid.RowActions
 * @extends Ext.util.Observable
 *
 * RowActions plugin for Ext grid. Contains renderer for icons and fires events when an icon is clicked.
 * CSS rules from Ext.ux.RowActions.css are mandatory
 *
 * Important general information: Actions are identified by iconCls. Wherever an <i>action</i>
 * is referenced (event argument, callback argument), the iconCls of clicked icon is used.
 * In other words, action identifier === iconCls.
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2008, by Ing. Jozef Sakáloš
 * @date      22. March 2008
 * @version   1.0
 * @revision  $Id: Ext.ux.grid.RowActions.js 529 2009-02-01 22:54:24Z jozo $
 *
 * @license Ext.ux.grid.RowActions is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     29961
 * @demo      http://rowactions.extjs.eu
 * @download  
 * <ul>
 * <li><a href="http://rowactions.extjs.eu/rowactions.tar.bz2">rowactions.tar.bz2</a></li>
 * <li><a href="http://rowactions.extjs.eu/rowactions.tar.gz">rowactions.tar.gz</a></li>
 * <li><a href="http://rowactions.extjs.eu/rowactions.zip">rowactions.zip</a></li>
 * </ul>
 */

Ext.ns('Ext.ux.grid');

// add RegExp.escape if it has not been already added
if('function' !== typeof RegExp.escape) {
  RegExp.escape = function(s) {
    if('string' !== typeof s) {
      return s;
    }
    // Note: if pasting from forum, precede ]/\ with backslash manually
    return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1');
  }; // eo function escape
}

/**
 * Creates new RowActions plugin
 * @constructor
 * @param {Object} config A config object
 */
Ext.ux.grid.RowActions = function(config) {
  Ext.apply(this, config);

  // {{{
  this.addEvents(
    /**
     * @event beforeaction
     * Fires before action event. Return false to cancel the subsequent action event.
     * @param {Ext.grid.GridPanel} grid
     * @param {Ext.data.Record} record Record corresponding to row clicked
     * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
     * @param {Integer} rowIndex Index of clicked grid row
     * @param {Integer} colIndex Index of clicked grid column that contains all action icons
     */
     'beforeaction'
    /**
     * @event action
     * Fires when icon is clicked
     * @param {Ext.grid.GridPanel} grid
     * @param {Ext.data.Record} record Record corresponding to row clicked
     * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
     * @param {Integer} rowIndex Index of clicked grid row
     * @param {Integer} colIndex Index of clicked grid column that contains all action icons
     */
    ,'action'
    /**
     * @event beforegroupaction
     * Fires before group action event. Return false to cancel the subsequent groupaction event.
     * @param {Ext.grid.GridPanel} grid
     * @param {Array} records Array of records in this group
     * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
     * @param {String} groupId Identifies the group clicked
     */
    ,'beforegroupaction'
    /**
     * @event groupaction
     * Fires when icon in a group header is clicked
     * @param {Ext.grid.GridPanel} grid
     * @param {Array} records Array of records in this group
     * @param {String} action Identifies the action icon clicked. Equals to icon css class name.
     * @param {String} groupId Identifies the group clicked
     */
    ,'groupaction'
  );
  // }}}

  // call parent
  Ext.ux.grid.RowActions.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {

  // configuration options
  // {{{
  /**
   * @cfg {Array} actions Mandatory. Array of action configuration objects. The following
   * configuration options of action are recognized:
   *
   * - @cfg {Function} callback Optional. Function to call if the action icon is clicked.
   *   This function is called with same signature as action event and in its original scope.
   *   If you need to call it in different scope or with another signature use 
   *   createCallback or createDelegate functions. Works for statically defined actions. Use
   *   callbacks configuration options for store bound actions.
   *
   * - @cfg {Function} cb Shortcut for callback.
   *
   * - @cfg {String} iconIndex Optional, however either iconIndex or iconCls must be
   *   configured. Field name of the field of the grid store record that contains
   *   css class of the icon to show. If configured, shown icons can vary depending
   *   of the value of this field.
   *
   * - @cfg {String} iconCls. css class of the icon to show. It is ignored if iconIndex is
   *   configured. Use this if you want static icons that are not base on the values in the record.
   *
   * - @cfg {Boolean} hide Optional. True to hide this action while still have a space in 
   *   the grid column allocated to it. IMO, it doesn't make too much sense, use hideIndex instead.
   *
   * - @cfg (string} hideIndex Optional. Field name of the field of the grid store record that
   *   contains hide flag (falsie [null, '', 0, false, undefined] to show, anything else to hide).
   *
   * - @cfg {String} qtipIndex Optional. Field name of the field of the grid store record that 
   *   contains tooltip text. If configured, the tooltip texts are taken from the store.
   *
   * - @cfg {String} tooltip Optional. Tooltip text to use as icon tooltip. It is ignored if 
   *   qtipIndex is configured. Use this if you want static tooltips that are not taken from the store.
   *
   * - @cfg {String} qtip Synonym for tooltip
   *
   * - @cfg {String} textIndex Optional. Field name of the field of the grids store record
   *   that contains text to display on the right side of the icon. If configured, the text
   *   shown is taken from record.
   *
   * - @cfg {String} text Optional. Text to display on the right side of the icon. Use this
   *   if you want static text that are not taken from record. Ignored if textIndex is set.
   *
   * - @cfg {String} style Optional. Style to apply to action icon container.
   */

  /**
   * @cfg {String} actionEvnet Event to trigger actions, e.g. click, dblclick, mouseover (defaults to 'click')
   */
   actionEvent:'click'

  /**
   * @cfg {Boolean} autoWidth true to calculate field width for iconic actions only.
   */
  ,autoWidth:true

  /**
   * @cfg {String} dataIndex - Do not touch!
   * @private
   */
  ,dataIndex:''

  /**
   * @cfg {Array} groupActions Array of action to use for group headers of grouping grids.
   * These actions support static icons, texts and tooltips same way as actions. There is one
   * more action config recognized:
   * - @cfg {String} align Set it to 'left' to place action icon next to the group header text.
   *   (defaults to undefined = icons are placed at the right side of the group header.
   */

  /**
   * @cfg {Object} callbacks iconCls keyed object that contains callback functions. For example:
   * callbacks:{
   *      'icon-open':function(...) {...}
   *     ,'icon-save':function(...) {...}
   * }
   */

  /**
   * @cfg {String} header Actions column header
   */
  ,header:''

  /**
   * @cfg {Boolean} menuDisabled No sense to display header menu for this column
   */
  ,menuDisabled:true

  /**
   * @cfg {Boolean} sortable Usually it has no sense to sort by this column
   */
  ,sortable:false

  /**
   * @cfg {String} tplGroup Template for group actions
   * @private
   */
  ,tplGroup:
     '<tpl for="actions">'
    +'<div class="ux-grow-action-item<tpl if="\'right\'===align"> ux-action-right</tpl> '
    +'{cls}" style="{style}" qtip="{qtip}">{text}</div>'
    +'</tpl>'

  /**
   * @cfg {String} tplRow Template for row actions
   * @private
   */
  ,tplRow:
     '<div class="ux-row-action">'
    +'<tpl for="actions">'
    +'<div class="ux-row-action-item {cls} <tpl if="text">'
    +'ux-row-action-text</tpl>" style="{hide}{style}" qtip="{qtip}">'
    +'<tpl if="text"><span qtip="{qtip}">{text}</span></tpl></div>'
    +'</tpl>'
    +'</div>'

  /**
   * @cfg {String} hideMode How to hide hidden icons. Valid values are: visibility and display
   * (defaluts to visibility).
   */
  ,hideMode:'visiblity'

  /**
   * @cfg {Number} widthIntercept constant used for auto-width calculation
   */
  ,widthIntercept:4

  /**
   * @cfg {Number} widthSlope constant used for auto-width calculation
   */
  ,widthSlope:25
  // }}}

  // methods
  // {{{
  /**
   * Init function
   * @param {Ext.grid.GridPanel} grid Grid this plugin is in
   */
  ,init:function(grid) {
    this.grid = grid;
    
    // {{{
    // setup template
    if(!this.tpl) {
      this.tpl = this.processActions(this.actions);

    } // eo template setup
    // }}}

    // calculate width
    if(this.autoWidth) {
      this.width =  this.widthSlope * this.actions.length + this.widthIntercept;
      this.fixed = true;
    }

    // body click handler
    var view = grid.getView();
    var cfg = {scope:this};
    cfg[this.actionEvent] = this.onClick;
    grid.afterRender = grid.afterRender.createSequence(function() {
      view.mainBody.on(cfg);
    }, this);

    // setup renderer
    if(!this.renderer) {
      this.renderer = function(value, cell, record, row, col, store) {
        cell.css += (cell.css ? ' ' : '') + 'ux-row-action-cell';
        return this.tpl.apply(this.getData(value, cell, record, row, col, store));
      }.createDelegate(this);
    }

    // actions in grouping grids support
    if(view.groupTextTpl && this.groupActions) {
      view.interceptMouse = view.interceptMouse.createInterceptor(function(e) {
        if(e.getTarget('.ux-grow-action-item')) {
          return false;
        }
      });
      view.groupTextTpl = 
         '<div class="ux-grow-action-text">' + view.groupTextTpl +'</div>' 
        +this.processActions(this.groupActions, this.tplGroup).apply()
      ;
    }
    
  } // eo function init
  // }}}
  // {{{
  /**
   * Returns data to apply to template. Override this if needed.
   * @param {Mixed} value 
   * @param {Object} cell object to set some attributes of the grid cell
   * @param {Ext.data.Record} record from which the data is extracted
   * @param {Number} row row index
   * @param {Number} col col index
   * @param {Ext.data.Store} store object from which the record is extracted
   * @returns {Object} data to apply to template
   */
  ,getData:function(value, cell, record, row, col, store) {
    return record.data || {};
  } // eo function getData
  // }}}
  // {{{
  /**
   * Processes actions configs and returns template.
   * @param {Array} actions
   * @param {String} template Optional. Template to use for one action item.
   * @return {String}
   * @private
   */
  ,processActions:function(actions, template) {
    var acts = [];

    // actions loop
    Ext.each(actions, function(a, i) {
      // save callback
      if(a.iconCls && 'function' === typeof (a.callback || a.cb)) {
        this.callbacks = this.callbacks || {};
        this.callbacks[a.iconCls] = a.callback || a.cb;
      }

      // data for intermediate template
      var o = {
         cls:a.iconIndex ? '{' + a.iconIndex + '}' : (a.iconCls ? a.iconCls : '')
        ,qtip:a.qtipIndex ? '{' + a.qtipIndex + '}' : (a.tooltip || a.qtip ? a.tooltip || a.qtip : '')
        ,text:a.textIndex ? '{' + a.textIndex + '}' : (a.text ? a.text : '')
        ,hide:a.hideIndex 
          ? '<tpl if="' + a.hideIndex + '">' 
            + ('display' === this.hideMode ? 'display:none' :'visibility:hidden') + ';</tpl>' 
          : (a.hide ? ('display' === this.hideMode ? 'display:none' :'visibility:hidden;') : '')
        ,align:a.align || 'right'
        ,style:a.style ? a.style : ''
      };
      acts.push(o);

    }, this); // eo actions loop

    var xt = new Ext.XTemplate(template || this.tplRow);
    return new Ext.XTemplate(xt.apply({actions:acts}));

  } // eo function processActions
  // }}}
  // {{{
  /**
   * Grid body actionEvent event handler
   * @private
   */
  ,onClick:function(e, target) {

    var view = this.grid.getView();
    var action = false;

    // handle row action click
    var row = e.getTarget('.x-grid3-row');
    var col = view.findCellIndex(target.parentNode.parentNode);

    var t = e.getTarget('.ux-row-action-item');
    if(t) {
      action = t.className.replace(/ux-row-action-item /, '');
      if(action) {
        action = action.replace(/ ux-row-action-text/, '');
        action = action.trim();
      }
    }
    if(false !== row && false !== col && false !== action) {
      var record = this.grid.store.getAt(row.rowIndex);

      // call callback if any
      if(this.callbacks && 'function' === typeof this.callbacks[action]) {
        this.callbacks[action](this.grid, record, action, row.rowIndex, col);
      }

      // fire events
      if(true !== this.eventsSuspended && false === this.fireEvent('beforeaction', this.grid, record, action, row.rowIndex, col)) {
        return;
      }
      else if(true !== this.eventsSuspended) {
        this.fireEvent('action', this.grid, record, action, row.rowIndex, col);
      }

    }

    // handle group action click
    t = e.getTarget('.ux-grow-action-item');
    if(t) {
      // get groupId
      var group = view.findGroup(target);
      var groupId = group ? group.id.replace(/ext-gen[0-9]+-gp-/, '') : null;

      // get matching records
      var records;
      if(groupId) {
        var re = new RegExp(RegExp.escape(groupId));
        records = this.grid.store.queryBy(function(r) {
          return r._groupId.match(re);
        });
        records = records ? records.items : [];
      }
      action = t.className.replace(/ux-grow-action-item (ux-action-right )*/, '');

      // call callback if any
      if('function' === typeof this.callbacks[action]) {
        this.callbacks[action](this.grid, records, action, groupId);
      }

      // fire events
      if(true !== this.eventsSuspended && false === this.fireEvent('beforegroupaction', this.grid, records, action, groupId)) {
        return false;
      }
      this.fireEvent('groupaction', this.grid, records, action, groupId);
    }
  } // eo function onClick
  // }}}

});

// registre xtype
Ext.reg('rowactions', Ext.ux.grid.RowActions);

// eof
