/*!
 * Ext JS Library 3.1.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.RowEditor
 * @extends Ext.Panel 
 * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
 * A validation mode may be enabled which uses AnchorTips to notify the user of all
 * validation errors at once.
 * 
 * @ptype roweditor
 */
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
  floating: true,
  shadow: false,
  layout: 'hbox',
  cls: 'x-small-editor',
  buttonAlign: 'center',
  baseCls: 'x-row-editor',
  elements: 'header,footer,body',
  frameWidth: 5,
  buttonPad: 3,
  clicksToEdit: 'auto',
  monitorValid: true,
  focusDelay: 250,
  errorSummary: true,

  saveText: 'Save',
  cancelText: 'Cancel',
  commitChangesText: 'You need to commit or cancel your changes',
  errorText: 'Errors',

  defaults: {
    normalWidth: true
  },

  initComponent: function() {
    Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
    this.addEvents(
    /**
    * @event beforeedit
    * Fired before the row editor is activated.
    * If the listener returns <tt>false</tt> the editor will not be activated.
    * @param {Ext.ux.grid.RowEditor} roweditor This object
    * @param {Number} rowIndex The rowIndex of the row just edited
    */
            'beforeedit',
    /**
    * @event canceledit
    * Fired when the editor is cancelled.
    * @param {Ext.ux.grid.RowEditor} roweditor This object
    * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid. 
    */
            'canceledit',
    /**
    * @event validateedit
    * Fired after a row is edited and passes validation.
    * If the listener returns <tt>false</tt> changes to the record will not be set.
    * @param {Ext.ux.grid.RowEditor} roweditor This object
    * @param {Object} changes Object with changes made to the record.
    * @param {Ext.data.Record} r The Record that was edited.
    * @param {Number} rowIndex The rowIndex of the row just edited
    */
            'validateedit',
    /**
    * @event afteredit
    * Fired after a row is edited and passes validation.  This event is fired
    * after the store's update event is fired with this edit.
    * @param {Ext.ux.grid.RowEditor} roweditor This object
    * @param {Object} changes Object with changes made to the record.
    * @param {Ext.data.Record} r The Record that was edited.
    * @param {Number} rowIndex The rowIndex of the row just edited
    */
            'afteredit'
        );
  },

  init: function(grid) {
    this.grid = grid;
    this.ownerCt = grid;
    if (this.clicksToEdit === 2) {
      grid.on('rowdblclick', this.onRowDblClick, this);
    } else {
      grid.on('rowclick', this.onRowClick, this);
      if (Ext.isIE) {
        grid.on('rowdblclick', this.onRowDblClick, this);
      }
    }

    // stopEditing without saving when a record is removed from Store.
    grid.getStore().on('remove', function() {
      this.stopEditing(false);
    }, this);

    grid.on({
      scope: this,
      keydown: this.onGridKey,
      columnresize: this.verifyLayout,
      columnmove: this.refreshFields,
      reconfigure: this.refreshFields,
      beforedestroy: this.beforedestroy,
      destroy: this.destroy,
      bodyscroll: {
        buffer: 250,
        fn: this.positionButtons
      }
    });
    grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, { delay: 1 });
    grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
  },

  beforedestroy: function() {
    this.grid.getStore().un('remove', this.onStoreRemove, this);
    this.stopEditing(false);
    Ext.destroy(this.btns);
  },

  refreshFields: function() {
    this.initFields();
    this.verifyLayout();
  },

  isDirty: function() {
    var dirty;
    this.items.each(function(f) {
      if (String(this.values[f.id]) !== String(f.getValue())) {
        dirty = true;
        return false;
      }
    }, this);
    return dirty;
  },

  startEditing: function(rowIndex, doFocus) {
    if (this.editing && this.isDirty()) {
      this.showTooltip(this.commitChangesText);
      return;
    }
    if (Ext.isObject(rowIndex)) {
      rowIndex = this.grid.getStore().indexOf(rowIndex);
    }
    if (this.fireEvent('beforeedit', this, rowIndex) !== false) {
      this.editing = true;
      var g = this.grid, view = g.getView(),
                row = view.getRow(rowIndex),
                record = g.store.getAt(rowIndex);

      this.record = record;
      this.rowIndex = rowIndex;
      this.values = {};
      if (!this.rendered) {
        this.render(view.getEditorParent());
      }
      var w = Ext.fly(row).getWidth();
      this.setSize(w);
      if (!this.initialized) {
        this.initFields();
      }
      var cm = g.getColumnModel(), fields = this.items.items, f, val;
      for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
        val = this.preEditValue(record, cm.getDataIndex(i));
        f = fields[i];

        //Daniel Fix for Tangane
        var column = cm.getColumnById(cm.getColumnId(i));
        if (column && (column.editor == null)) { // ReadOnly field only
          if (column.renderer != null) {
            val = column.renderer.call(column, val, {}, record, rowIndex);
          }
        }
        // End Daniel Fix for Tangane

        f.setValue(val);
        this.values[f.id] = Ext.isEmpty(val) ? '' : val;
      }
      this.verifyLayout(true);
      if (!this.isVisible()) {
        this.setPagePosition(Ext.fly(row).getXY());
      } else {
        this.el.setXY(Ext.fly(row).getXY(), { duration: 0.15 });
      }
      if (!this.isVisible()) {
        this.show().doLayout();
      }
      if (doFocus !== false) {
        this.doFocus.defer(this.focusDelay, this);
      }
    }
  },

  stopEditing: function(saveChanges) {
    this.editing = false;
    if (!this.isVisible()) {
      return;
    }
    if (saveChanges === false || !this.isValid()) {
      this.hide();
      this.fireEvent('canceledit', this, saveChanges === false);
      return;
    }
    var changes = {},
            r = this.record,
            hasChange = false,
            cm = this.grid.colModel,
            fields = this.items.items;
    for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
      if (!cm.isHidden(i)) {
        var dindex = cm.getDataIndex(i);
        if (!Ext.isEmpty(dindex)) {
          var oldValue = r.data[dindex],
                        value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
          if (String(oldValue) !== String(value)) {
            changes[dindex] = value;
            hasChange = true;
          }
        }
      }
    }
    if (hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false) {
      r.beginEdit();
      Ext.iterate(changes, function(name, value) {
        r.set(name, value);
      });
      r.endEdit();
      this.fireEvent('afteredit', this, changes, r, this.rowIndex);
    }
    this.hide();
  },

  verifyLayout: function(force) {
    if (this.el && (this.isVisible() || force === true)) {
      var row = this.grid.getView().getRow(this.rowIndex);
      this.setSize(Ext.fly(row).getWidth(), Ext.fly(row).getHeight() + 9);
      var cm = this.grid.colModel, fields = this.items.items;
      for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
        if (!cm.isHidden(i)) {
          var adjust = 0;
          if (i === (len - 1)) {
            adjust += 3; // outer padding
          } else {
            adjust += 1;
          }
          fields[i].show();
          fields[i].setWidth(cm.getColumnWidth(i) - adjust);
        } else {
          fields[i].hide();
        }
      }
      this.doLayout();
      this.positionButtons();
    }
  },

  slideHide: function() {
    this.hide();
  },

  initFields: function() {
    var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
    this.removeAll(false);
    for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
      var c = cm.getColumnAt(i),
                ed = c.getEditor();
      if (!ed) {
        ed = c.displayEditor || new Ext.form.DisplayField();
      }
      if (i == 0) {
        ed.margins = pm('0 1 2 1');
      } else if (i == len - 1) {
        ed.margins = pm('0 0 2 1');
      } else {
        ed.margins = pm('0 1 2');
      }
      ed.setWidth(cm.getColumnWidth(i));
      ed.column = c;
      if (ed.ownerCt !== this) {
        ed.on('focus', this.ensureVisible, this);
        ed.on('specialkey', this.onKey, this);
      }
      this.insert(i, ed);
    }
    this.initialized = true;
  },

  onKey: function(f, e) {
    if (e.getKey() === e.ENTER) {
      this.stopEditing(true);
      e.stopPropagation();
    }
  },

  onGridKey: function(e) {
    if (e.getKey() === e.ENTER && !this.isVisible()) {
      var r = this.grid.getSelectionModel().getSelected();
      if (r) {
        var index = this.grid.store.indexOf(r);
        this.startEditing(index);
        e.stopPropagation();
      }
    }
  },

  ensureVisible: function(editor) {
    if (this.isVisible()) {
      this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
    }
  },

  onRowClick: function(g, rowIndex, e) {
    if (this.clicksToEdit == 'auto') {
      var li = this.lastClickIndex;
      this.lastClickIndex = rowIndex;
      if (li != rowIndex && !this.isVisible()) {
        return;
      }
    }
    this.startEditing(rowIndex, false);
    this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
  },

  onRowDblClick: function(g, rowIndex, e) {
    this.startEditing(rowIndex, false);
    this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
  },

  onRender: function() {
    Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
    this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
    this.btns = new Ext.Panel({
      baseCls: 'x-plain',
      cls: 'x-btns',
      elements: 'body',
      layout: 'table',
      width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
      items: [{
        ref: 'saveBtn',
        itemId: 'saveBtn',
        xtype: 'button',
        text: this.saveText,
        width: this.minButtonWidth,
        handler: this.stopEditing.createDelegate(this, [true])
      }, {
        xtype: 'button',
        text: this.cancelText,
        width: this.minButtonWidth,
        handler: this.stopEditing.createDelegate(this, [false])
}]
      });
      this.btns.render(this.bwrap);
    },

    afterRender: function() {
      Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
      this.positionButtons();
      if (this.monitorValid) {
        this.startMonitoring();
      }
    },

    onShow: function() {
      if (this.monitorValid) {
        this.startMonitoring();
      }
      Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
    },

    onHide: function() {
      Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
      this.stopMonitoring();
      this.grid.getView().focusRow(this.rowIndex);
    },

    positionButtons: function() {
      if (this.btns) {
        var g = this.grid,
                h = this.el.dom.clientHeight,
                view = g.getView(),
                scroll = view.scroller.dom.scrollLeft,
                bw = this.btns.getWidth(),
                width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());

        this.btns.el.shift({ left: (width / 2) - (bw / 2) + scroll, top: h - 2, stopFx: true, duration: 0.2 });
      }
    },

    // private
    preEditValue: function(r, field) {
      var value = r.data[field];
      return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
    },

    // private
    postEditValue: function(value, originalValue, r, field) {
      return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
    },

    doFocus: function(pt) {
      if (this.isVisible()) {
        var index = 0,
                cm = this.grid.getColumnModel(),
                c;
        if (pt) {
          index = this.getTargetColumnIndex(pt);
        }
        for (var i = index || 0, len = cm.getColumnCount(); i < len; i++) {
          c = cm.getColumnAt(i);
          if (!c.hidden && c.getEditor()) {
            c.getEditor().focus();
            break;
          }
        }
      }
    },

    getTargetColumnIndex: function(pt) {
      var grid = this.grid,
            v = grid.view,
            x = pt.left,
            cms = grid.colModel.config,
            i = 0,
            match = false;
      for (var len = cms.length, c; c = cms[i]; i++) {
        if (!c.hidden) {
          if (Ext.fly(v.getHeaderCell(i)).getRegion().right >= x) {
            match = i;
            break;
          }
        }
      }
      return match;
    },

    startMonitoring: function() {
      if (!this.bound && this.monitorValid) {
        this.bound = true;
        Ext.TaskMgr.start({
          run: this.bindHandler,
          interval: this.monitorPoll || 200,
          scope: this
        });
      }
    },

    stopMonitoring: function() {
      this.bound = false;
      if (this.tooltip) {
        this.tooltip.hide();
      }
    },

    isValid: function() {
      var valid = true;
      this.items.each(function(f) {
        if (!f.isValid(true)) {
          valid = false;
          return false;
        }
      });
      return valid;
    },

    // private
    bindHandler: function() {
      if (!this.bound) {
        return false; // stops binding
      }
      var valid = this.isValid();
      if (!valid && this.errorSummary) {
        this.showTooltip(this.getErrorText().join(''));
      }
      this.btns.saveBtn.setDisabled(!valid);
      this.fireEvent('validation', this, valid);
    },

    showTooltip: function(msg) {
      var t = this.tooltip;
      if (!t) {
        t = this.tooltip = new Ext.ToolTip({
          maxWidth: 600,
          cls: 'errorTip',
          width: 300,
          title: this.errorText,
          autoHide: false,
          anchor: 'left',
          anchorToTarget: true,
          mouseOffset: [40, 0]
        });
      }
      var v = this.grid.getView(),
            top = parseInt(this.el.dom.style.top, 10),
            scroll = v.scroller.dom.scrollTop,
            h = this.el.getHeight();

      if (top + h >= scroll) {
        t.initTarget(this.items.last().getEl());
        if (!t.rendered) {
          t.show();
          t.hide();
        }
        t.body.update(msg);
        t.doAutoWidth(20);
        t.show();
      } else if (t.rendered) {
        t.hide();
      }
    },

    getErrorText: function() {
      var data = ['<ul>'];
      this.items.each(function(f) {
        if (!f.isValid(true)) {
          data.push('<li>', f.getActiveError(), '</li>');
        }
      });
      data.push('</ul>');
      return data;
    }
  });
Ext.preg('roweditor', Ext.ux.grid.RowEditor);

