2 * Contains core SlickGrid classes.
9 $.extend(true, window, {
12 "EventData": EventData,
13 "EventHandler": EventHandler,
15 "NonDataRow": NonDataItem,
17 "GroupTotals": GroupTotals,
18 "EditorLock": EditorLock,
21 * A global singleton editor lock.
22 * @class GlobalEditorLock
26 "GlobalEditorLock": new EditorLock()
31 * An event object for passing data to event handlers and letting them control propagation.
32 * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
36 function EventData() {
37 var isPropagationStopped = false;
38 var isImmediatePropagationStopped = false;
41 * Stops event from propagating up the DOM tree.
42 * @method stopPropagation
44 this.stopPropagation = function () {
45 isPropagationStopped = true;
49 * Returns whether stopPropagation was called on this event object.
50 * @method isPropagationStopped
53 this.isPropagationStopped = function () {
54 return isPropagationStopped;
58 * Prevents the rest of the handlers from being executed.
59 * @method stopImmediatePropagation
61 this.stopImmediatePropagation = function () {
62 isImmediatePropagationStopped = true;
66 * Returns whether stopImmediatePropagation was called on this event object.\
67 * @method isImmediatePropagationStopped
70 this.isImmediatePropagationStopped = function () {
71 return isImmediatePropagationStopped;
76 * A simple publisher-subscriber implementation.
84 * Adds an event handler to be called when the event is fired.
85 * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
86 * object the event was fired with.<p>
88 * @param fn {Function} Event handler.
90 this.subscribe = function (fn) {
95 * Removes an event handler added with <code>subscribe(fn)</code>.
97 * @param fn {Function} Event handler to be removed.
99 this.unsubscribe = function (fn) {
100 for (var i = handlers.length - 1; i >= 0; i--) {
101 if (handlers[i] === fn) {
102 handlers.splice(i, 1);
108 * Fires an event notifying all subscribers.
110 * @param args {Object} Additional data object to be passed to all handlers.
111 * @param e {EventData}
113 * An <code>EventData</code> object to be passed to all handlers.
114 * For DOM events, an existing W3C/jQuery event object can be passed in.
115 * @param scope {Object}
117 * The scope ("this") within which the handler will be executed.
118 * If not specified, the scope will be set to the <code>Event</code> instance.
120 this.notify = function (args, e, scope) {
121 e = e || new EventData();
122 scope = scope || this;
125 for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
126 returnValue = handlers[i].call(scope, e, args);
133 function EventHandler() {
136 this.subscribe = function (event, handler) {
141 event.subscribe(handler);
143 return this; // allow chaining
146 this.unsubscribe = function (event, handler) {
147 var i = handlers.length;
149 if (handlers[i].event === event &&
150 handlers[i].handler === handler) {
151 handlers.splice(i, 1);
152 event.unsubscribe(handler);
157 return this; // allow chaining
160 this.unsubscribeAll = function () {
161 var i = handlers.length;
163 handlers[i].event.unsubscribe(handlers[i].handler);
167 return this; // allow chaining
172 * A structure containing a range of cells.
175 * @param fromRow {Integer} Starting row.
176 * @param fromCell {Integer} Starting cell.
177 * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
178 * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
180 function Range(fromRow, fromCell, toRow, toCell) {
181 if (toRow === undefined && toCell === undefined) {
190 this.fromRow = Math.min(fromRow, toRow);
196 this.fromCell = Math.min(fromCell, toCell);
202 this.toRow = Math.max(fromRow, toRow);
208 this.toCell = Math.max(fromCell, toCell);
211 * Returns whether a range represents a single row.
212 * @method isSingleRow
215 this.isSingleRow = function () {
216 return this.fromRow == this.toRow;
220 * Returns whether a range represents a single cell.
221 * @method isSingleCell
224 this.isSingleCell = function () {
225 return this.fromRow == this.toRow && this.fromCell == this.toCell;
229 * Returns whether a range contains a given cell.
231 * @param row {Integer}
232 * @param cell {Integer}
235 this.contains = function (row, cell) {
236 return row >= this.fromRow && row <= this.toRow &&
237 cell >= this.fromCell && cell <= this.toCell;
241 * Returns a readable representation of a range.
245 this.toString = function () {
246 if (this.isSingleCell()) {
247 return "(" + this.fromRow + ":" + this.fromCell + ")";
250 return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
257 * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
261 function NonDataItem() {
262 this.__nonDataRow = true;
267 * Information about a group of rows.
269 * @extends Slick.NonDataItem
276 * Grouping level, starting with 0.
283 * Number of rows in the group.
297 * Formatted display value of the group.
304 * Whether a group is collapsed.
305 * @property collapsed
308 this.collapsed = false;
311 * GroupTotals, if any.
313 * @type {GroupTotals}
318 * Rows that are part of the group.
325 * Sub-groups that are part of the group.
332 * A unique key used to identify the group. This key can be used in calls to DataView
333 * collapseGroup() or expandGroup().
334 * @property groupingKey
337 this.groupingKey = null;
340 Group.prototype = new NonDataItem();
343 * Compares two Group instances.
346 * @param group {Group} Group instance to compare to.
348 Group.prototype.equals = function (group) {
349 return this.value === group.value &&
350 this.count === group.count &&
351 this.collapsed === group.collapsed;
355 * Information about group totals.
356 * An instance of GroupTotals will be created for each totals row and passed to the aggregators
357 * so that they can store arbitrary data in it. That data can later be accessed by group totals
358 * formatters during the display.
360 * @extends Slick.NonDataItem
363 function GroupTotals() {
364 this.__groupTotals = true;
374 GroupTotals.prototype = new NonDataItem();
377 * A locking helper to track the active edit controller and ensure that only a single controller
378 * can be active at a time. This prevents a whole class of state and validation synchronization
379 * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress
380 * and attempt a commit or cancel before proceeding.
384 function EditorLock() {
385 var activeEditController = null;
388 * Returns true if a specified edit controller is active (has the edit lock).
389 * If the parameter is not specified, returns true if any edit controller is active.
391 * @param editController {EditController}
394 this.isActive = function (editController) {
395 return (editController ? activeEditController === editController : activeEditController !== null);
399 * Sets the specified edit controller as the active edit controller (acquire edit lock).
400 * If another edit controller is already active, and exception will be thrown.
402 * @param editController {EditController} edit controller acquiring the lock
404 this.activate = function (editController) {
405 if (editController === activeEditController) { // already activated?
408 if (activeEditController !== null) {
409 throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
411 if (!editController.commitCurrentEdit) {
412 throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
414 if (!editController.cancelCurrentEdit) {
415 throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
417 activeEditController = editController;
421 * Unsets the specified edit controller as the active edit controller (release edit lock).
422 * If the specified edit controller is not the active one, an exception will be thrown.
424 * @param editController {EditController} edit controller releasing the lock
426 this.deactivate = function (editController) {
427 if (activeEditController !== editController) {
428 throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
430 activeEditController = null;
434 * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
435 * controller and returns whether the commit attempt was successful (commit may fail due to validation
436 * errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
437 * and false otherwise. If no edit controller is active, returns true.
438 * @method commitCurrentEdit
441 this.commitCurrentEdit = function () {
442 return (activeEditController ? activeEditController.commitCurrentEdit() : true);
446 * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
447 * controller and returns whether the edit was successfully cancelled. If no edit controller is
448 * active, returns true.
449 * @method cancelCurrentEdit
452 this.cancelCurrentEdit = function cancelCurrentEdit() {
453 return (activeEditController ? activeEditController.cancelCurrentEdit() : true);