first step for manual merging of the slickgrid branch
[myslice.git] / third-party / slickgrid-2.1 / slick.core.js
diff --git a/third-party/slickgrid-2.1/slick.core.js b/third-party/slickgrid-2.1/slick.core.js
new file mode 100644 (file)
index 0000000..5c4c695
--- /dev/null
@@ -0,0 +1,458 @@
+/***
+ * Contains core SlickGrid classes.
+ * @module Core
+ * @namespace Slick
+ */
+
+(function ($) {
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "Event": Event,
+      "EventData": EventData,
+      "EventHandler": EventHandler,
+      "Range": Range,
+      "NonDataRow": NonDataItem,
+      "Group": Group,
+      "GroupTotals": GroupTotals,
+      "EditorLock": EditorLock,
+
+      /***
+       * A global singleton editor lock.
+       * @class GlobalEditorLock
+       * @static
+       * @constructor
+       */
+      "GlobalEditorLock": new EditorLock()
+    }
+  });
+
+  /***
+   * An event object for passing data to event handlers and letting them control propagation.
+   * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
+   * @class EventData
+   * @constructor
+   */
+  function EventData() {
+    var isPropagationStopped = false;
+    var isImmediatePropagationStopped = false;
+
+    /***
+     * Stops event from propagating up the DOM tree.
+     * @method stopPropagation
+     */
+    this.stopPropagation = function () {
+      isPropagationStopped = true;
+    };
+
+    /***
+     * Returns whether stopPropagation was called on this event object.
+     * @method isPropagationStopped
+     * @return {Boolean}
+     */
+    this.isPropagationStopped = function () {
+      return isPropagationStopped;
+    };
+
+    /***
+     * Prevents the rest of the handlers from being executed.
+     * @method stopImmediatePropagation
+     */
+    this.stopImmediatePropagation = function () {
+      isImmediatePropagationStopped = true;
+    };
+
+    /***
+     * Returns whether stopImmediatePropagation was called on this event object.\
+     * @method isImmediatePropagationStopped
+     * @return {Boolean}
+     */
+    this.isImmediatePropagationStopped = function () {
+      return isImmediatePropagationStopped;
+    }
+  }
+
+  /***
+   * A simple publisher-subscriber implementation.
+   * @class Event
+   * @constructor
+   */
+  function Event() {
+    var handlers = [];
+
+    /***
+     * Adds an event handler to be called when the event is fired.
+     * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
+     * object the event was fired with.<p>
+     * @method subscribe
+     * @param fn {Function} Event handler.
+     */
+    this.subscribe = function (fn) {
+      handlers.push(fn);
+    };
+
+    /***
+     * Removes an event handler added with <code>subscribe(fn)</code>.
+     * @method unsubscribe
+     * @param fn {Function} Event handler to be removed.
+     */
+    this.unsubscribe = function (fn) {
+      for (var i = handlers.length - 1; i >= 0; i--) {
+        if (handlers[i] === fn) {
+          handlers.splice(i, 1);
+        }
+      }
+    };
+
+    /***
+     * Fires an event notifying all subscribers.
+     * @method notify
+     * @param args {Object} Additional data object to be passed to all handlers.
+     * @param e {EventData}
+     *      Optional.
+     *      An <code>EventData</code> object to be passed to all handlers.
+     *      For DOM events, an existing W3C/jQuery event object can be passed in.
+     * @param scope {Object}
+     *      Optional.
+     *      The scope ("this") within which the handler will be executed.
+     *      If not specified, the scope will be set to the <code>Event</code> instance.
+     */
+    this.notify = function (args, e, scope) {
+      e = e || new EventData();
+      scope = scope || this;
+
+      var returnValue;
+      for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
+        returnValue = handlers[i].call(scope, e, args);
+      }
+
+      return returnValue;
+    };
+  }
+
+  function EventHandler() {
+    var handlers = [];
+
+    this.subscribe = function (event, handler) {
+      handlers.push({
+        event: event,
+        handler: handler
+      });
+      event.subscribe(handler);
+
+      return this;  // allow chaining
+    };
+
+    this.unsubscribe = function (event, handler) {
+      var i = handlers.length;
+      while (i--) {
+        if (handlers[i].event === event &&
+            handlers[i].handler === handler) {
+          handlers.splice(i, 1);
+          event.unsubscribe(handler);
+          return;
+        }
+      }
+
+      return this;  // allow chaining
+    };
+
+    this.unsubscribeAll = function () {
+      var i = handlers.length;
+      while (i--) {
+        handlers[i].event.unsubscribe(handlers[i].handler);
+      }
+      handlers = [];
+
+      return this;  // allow chaining
+    }
+  }
+
+  /***
+   * A structure containing a range of cells.
+   * @class Range
+   * @constructor
+   * @param fromRow {Integer} Starting row.
+   * @param fromCell {Integer} Starting cell.
+   * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
+   * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
+   */
+  function Range(fromRow, fromCell, toRow, toCell) {
+    if (toRow === undefined && toCell === undefined) {
+      toRow = fromRow;
+      toCell = fromCell;
+    }
+
+    /***
+     * @property fromRow
+     * @type {Integer}
+     */
+    this.fromRow = Math.min(fromRow, toRow);
+
+    /***
+     * @property fromCell
+     * @type {Integer}
+     */
+    this.fromCell = Math.min(fromCell, toCell);
+
+    /***
+     * @property toRow
+     * @type {Integer}
+     */
+    this.toRow = Math.max(fromRow, toRow);
+
+    /***
+     * @property toCell
+     * @type {Integer}
+     */
+    this.toCell = Math.max(fromCell, toCell);
+
+    /***
+     * Returns whether a range represents a single row.
+     * @method isSingleRow
+     * @return {Boolean}
+     */
+    this.isSingleRow = function () {
+      return this.fromRow == this.toRow;
+    };
+
+    /***
+     * Returns whether a range represents a single cell.
+     * @method isSingleCell
+     * @return {Boolean}
+     */
+    this.isSingleCell = function () {
+      return this.fromRow == this.toRow && this.fromCell == this.toCell;
+    };
+
+    /***
+     * Returns whether a range contains a given cell.
+     * @method contains
+     * @param row {Integer}
+     * @param cell {Integer}
+     * @return {Boolean}
+     */
+    this.contains = function (row, cell) {
+      return row >= this.fromRow && row <= this.toRow &&
+          cell >= this.fromCell && cell <= this.toCell;
+    };
+
+    /***
+     * Returns a readable representation of a range.
+     * @method toString
+     * @return {String}
+     */
+    this.toString = function () {
+      if (this.isSingleCell()) {
+        return "(" + this.fromRow + ":" + this.fromCell + ")";
+      }
+      else {
+        return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
+      }
+    }
+  }
+
+
+  /***
+   * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
+   * @class NonDataItem
+   * @constructor
+   */
+  function NonDataItem() {
+    this.__nonDataRow = true;
+  }
+
+
+  /***
+   * Information about a group of rows.
+   * @class Group
+   * @extends Slick.NonDataItem
+   * @constructor
+   */
+  function Group() {
+    this.__group = true;
+
+    /**
+     * Grouping level, starting with 0.
+     * @property level
+     * @type {Number}
+     */
+    this.level = 0;
+
+    /***
+     * Number of rows in the group.
+     * @property count
+     * @type {Integer}
+     */
+    this.count = 0;
+
+    /***
+     * Grouping value.
+     * @property value
+     * @type {Object}
+     */
+    this.value = null;
+
+    /***
+     * Formatted display value of the group.
+     * @property title
+     * @type {String}
+     */
+    this.title = null;
+
+    /***
+     * Whether a group is collapsed.
+     * @property collapsed
+     * @type {Boolean}
+     */
+    this.collapsed = false;
+
+    /***
+     * GroupTotals, if any.
+     * @property totals
+     * @type {GroupTotals}
+     */
+    this.totals = null;
+
+    /**
+     * Rows that are part of the group.
+     * @property rows
+     * @type {Array}
+     */
+    this.rows = [];
+
+    /**
+     * Sub-groups that are part of the group.
+     * @property groups
+     * @type {Array}
+     */
+    this.groups = null;
+
+    /**
+     * A unique key used to identify the group.  This key can be used in calls to DataView
+     * collapseGroup() or expandGroup().
+     * @property groupingKey
+     * @type {Object}
+     */
+    this.groupingKey = null;
+  }
+
+  Group.prototype = new NonDataItem();
+
+  /***
+   * Compares two Group instances.
+   * @method equals
+   * @return {Boolean}
+   * @param group {Group} Group instance to compare to.
+   */
+  Group.prototype.equals = function (group) {
+    return this.value === group.value &&
+        this.count === group.count &&
+        this.collapsed === group.collapsed;
+  };
+
+  /***
+   * Information about group totals.
+   * An instance of GroupTotals will be created for each totals row and passed to the aggregators
+   * so that they can store arbitrary data in it.  That data can later be accessed by group totals
+   * formatters during the display.
+   * @class GroupTotals
+   * @extends Slick.NonDataItem
+   * @constructor
+   */
+  function GroupTotals() {
+    this.__groupTotals = true;
+
+    /***
+     * Parent Group.
+     * @param group
+     * @type {Group}
+     */
+    this.group = null;
+  }
+
+  GroupTotals.prototype = new NonDataItem();
+
+  /***
+   * A locking helper to track the active edit controller and ensure that only a single controller
+   * can be active at a time.  This prevents a whole class of state and validation synchronization
+   * issues.  An edit controller (such as SlickGrid) can query if an active edit is in progress
+   * and attempt a commit or cancel before proceeding.
+   * @class EditorLock
+   * @constructor
+   */
+  function EditorLock() {
+    var activeEditController = null;
+
+    /***
+     * Returns true if a specified edit controller is active (has the edit lock).
+     * If the parameter is not specified, returns true if any edit controller is active.
+     * @method isActive
+     * @param editController {EditController}
+     * @return {Boolean}
+     */
+    this.isActive = function (editController) {
+      return (editController ? activeEditController === editController : activeEditController !== null);
+    };
+
+    /***
+     * Sets the specified edit controller as the active edit controller (acquire edit lock).
+     * If another edit controller is already active, and exception will be thrown.
+     * @method activate
+     * @param editController {EditController} edit controller acquiring the lock
+     */
+    this.activate = function (editController) {
+      if (editController === activeEditController) { // already activated?
+        return;
+      }
+      if (activeEditController !== null) {
+        throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
+      }
+      if (!editController.commitCurrentEdit) {
+        throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
+      }
+      if (!editController.cancelCurrentEdit) {
+        throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
+      }
+      activeEditController = editController;
+    };
+
+    /***
+     * Unsets the specified edit controller as the active edit controller (release edit lock).
+     * If the specified edit controller is not the active one, an exception will be thrown.
+     * @method deactivate
+     * @param editController {EditController} edit controller releasing the lock
+     */
+    this.deactivate = function (editController) {
+      if (activeEditController !== editController) {
+        throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
+      }
+      activeEditController = null;
+    };
+
+    /***
+     * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
+     * controller and returns whether the commit attempt was successful (commit may fail due to validation
+     * errors, etc.).  Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
+     * and false otherwise.  If no edit controller is active, returns true.
+     * @method commitCurrentEdit
+     * @return {Boolean}
+     */
+    this.commitCurrentEdit = function () {
+      return (activeEditController ? activeEditController.commitCurrentEdit() : true);
+    };
+
+    /***
+     * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
+     * controller and returns whether the edit was successfully cancelled.  If no edit controller is
+     * active, returns true.
+     * @method cancelCurrentEdit
+     * @return {Boolean}
+     */
+    this.cancelCurrentEdit = function cancelCurrentEdit() {
+      return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
+    };
+  }
+})(jQuery);
+
+