Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 
 

459 righe
12 KiB

  1. /***
  2. * Contains core SlickGrid classes.
  3. * @module Core
  4. * @namespace Slick
  5. */
  6. (function ($) {
  7. // register namespace
  8. $.extend(true, window, {
  9. "Slick": {
  10. "Event": Event,
  11. "EventData": EventData,
  12. "EventHandler": EventHandler,
  13. "Range": Range,
  14. "NonDataRow": NonDataItem,
  15. "Group": Group,
  16. "GroupTotals": GroupTotals,
  17. "EditorLock": EditorLock,
  18. /***
  19. * A global singleton editor lock.
  20. * @class GlobalEditorLock
  21. * @static
  22. * @constructor
  23. */
  24. "GlobalEditorLock": new EditorLock()
  25. }
  26. });
  27. /***
  28. * An event object for passing data to event handlers and letting them control propagation.
  29. * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
  30. * @class EventData
  31. * @constructor
  32. */
  33. function EventData() {
  34. var isPropagationStopped = false;
  35. var isImmediatePropagationStopped = false;
  36. /***
  37. * Stops event from propagating up the DOM tree.
  38. * @method stopPropagation
  39. */
  40. this.stopPropagation = function () {
  41. isPropagationStopped = true;
  42. };
  43. /***
  44. * Returns whether stopPropagation was called on this event object.
  45. * @method isPropagationStopped
  46. * @return {Boolean}
  47. */
  48. this.isPropagationStopped = function () {
  49. return isPropagationStopped;
  50. };
  51. /***
  52. * Prevents the rest of the handlers from being executed.
  53. * @method stopImmediatePropagation
  54. */
  55. this.stopImmediatePropagation = function () {
  56. isImmediatePropagationStopped = true;
  57. };
  58. /***
  59. * Returns whether stopImmediatePropagation was called on this event object.\
  60. * @method isImmediatePropagationStopped
  61. * @return {Boolean}
  62. */
  63. this.isImmediatePropagationStopped = function () {
  64. return isImmediatePropagationStopped;
  65. }
  66. }
  67. /***
  68. * A simple publisher-subscriber implementation.
  69. * @class Event
  70. * @constructor
  71. */
  72. function Event() {
  73. var handlers = [];
  74. /***
  75. * Adds an event handler to be called when the event is fired.
  76. * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
  77. * object the event was fired with.<p>
  78. * @method subscribe
  79. * @param fn {Function} Event handler.
  80. */
  81. this.subscribe = function (fn) {
  82. handlers.push(fn);
  83. };
  84. /***
  85. * Removes an event handler added with <code>subscribe(fn)</code>.
  86. * @method unsubscribe
  87. * @param fn {Function} Event handler to be removed.
  88. */
  89. this.unsubscribe = function (fn) {
  90. for (var i = handlers.length - 1; i >= 0; i--) {
  91. if (handlers[i] === fn) {
  92. handlers.splice(i, 1);
  93. }
  94. }
  95. };
  96. /***
  97. * Fires an event notifying all subscribers.
  98. * @method notify
  99. * @param args {Object} Additional data object to be passed to all handlers.
  100. * @param e {EventData}
  101. * Optional.
  102. * An <code>EventData</code> object to be passed to all handlers.
  103. * For DOM events, an existing W3C/jQuery event object can be passed in.
  104. * @param scope {Object}
  105. * Optional.
  106. * The scope ("this") within which the handler will be executed.
  107. * If not specified, the scope will be set to the <code>Event</code> instance.
  108. */
  109. this.notify = function (args, e, scope) {
  110. e = e || new EventData();
  111. scope = scope || this;
  112. var returnValue;
  113. for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
  114. returnValue = handlers[i].call(scope, e, args);
  115. }
  116. return returnValue;
  117. };
  118. }
  119. function EventHandler() {
  120. var handlers = [];
  121. this.subscribe = function (event, handler) {
  122. handlers.push({
  123. event: event,
  124. handler: handler
  125. });
  126. event.subscribe(handler);
  127. return this; // allow chaining
  128. };
  129. this.unsubscribe = function (event, handler) {
  130. var i = handlers.length;
  131. while (i--) {
  132. if (handlers[i].event === event &&
  133. handlers[i].handler === handler) {
  134. handlers.splice(i, 1);
  135. event.unsubscribe(handler);
  136. return;
  137. }
  138. }
  139. return this; // allow chaining
  140. };
  141. this.unsubscribeAll = function () {
  142. var i = handlers.length;
  143. while (i--) {
  144. handlers[i].event.unsubscribe(handlers[i].handler);
  145. }
  146. handlers = [];
  147. return this; // allow chaining
  148. }
  149. }
  150. /***
  151. * A structure containing a range of cells.
  152. * @class Range
  153. * @constructor
  154. * @param fromRow {Integer} Starting row.
  155. * @param fromCell {Integer} Starting cell.
  156. * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
  157. * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
  158. */
  159. function Range(fromRow, fromCell, toRow, toCell) {
  160. if (toRow === undefined && toCell === undefined) {
  161. toRow = fromRow;
  162. toCell = fromCell;
  163. }
  164. /***
  165. * @property fromRow
  166. * @type {Integer}
  167. */
  168. this.fromRow = Math.min(fromRow, toRow);
  169. /***
  170. * @property fromCell
  171. * @type {Integer}
  172. */
  173. this.fromCell = Math.min(fromCell, toCell);
  174. /***
  175. * @property toRow
  176. * @type {Integer}
  177. */
  178. this.toRow = Math.max(fromRow, toRow);
  179. /***
  180. * @property toCell
  181. * @type {Integer}
  182. */
  183. this.toCell = Math.max(fromCell, toCell);
  184. /***
  185. * Returns whether a range represents a single row.
  186. * @method isSingleRow
  187. * @return {Boolean}
  188. */
  189. this.isSingleRow = function () {
  190. return this.fromRow == this.toRow;
  191. };
  192. /***
  193. * Returns whether a range represents a single cell.
  194. * @method isSingleCell
  195. * @return {Boolean}
  196. */
  197. this.isSingleCell = function () {
  198. return this.fromRow == this.toRow && this.fromCell == this.toCell;
  199. };
  200. /***
  201. * Returns whether a range contains a given cell.
  202. * @method contains
  203. * @param row {Integer}
  204. * @param cell {Integer}
  205. * @return {Boolean}
  206. */
  207. this.contains = function (row, cell) {
  208. return row >= this.fromRow && row <= this.toRow &&
  209. cell >= this.fromCell && cell <= this.toCell;
  210. };
  211. /***
  212. * Returns a readable representation of a range.
  213. * @method toString
  214. * @return {String}
  215. */
  216. this.toString = function () {
  217. if (this.isSingleCell()) {
  218. return "(" + this.fromRow + ":" + this.fromCell + ")";
  219. }
  220. else {
  221. return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
  222. }
  223. }
  224. }
  225. /***
  226. * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
  227. * @class NonDataItem
  228. * @constructor
  229. */
  230. function NonDataItem() {
  231. this.__nonDataRow = true;
  232. }
  233. /***
  234. * Information about a group of rows.
  235. * @class Group
  236. * @extends Slick.NonDataItem
  237. * @constructor
  238. */
  239. function Group() {
  240. this.__group = true;
  241. /**
  242. * Grouping level, starting with 0.
  243. * @property level
  244. * @type {Number}
  245. */
  246. this.level = 0;
  247. /***
  248. * Number of rows in the group.
  249. * @property count
  250. * @type {Integer}
  251. */
  252. this.count = 0;
  253. /***
  254. * Grouping value.
  255. * @property value
  256. * @type {Object}
  257. */
  258. this.value = null;
  259. /***
  260. * Formatted display value of the group.
  261. * @property title
  262. * @type {String}
  263. */
  264. this.title = null;
  265. /***
  266. * Whether a group is collapsed.
  267. * @property collapsed
  268. * @type {Boolean}
  269. */
  270. this.collapsed = false;
  271. /***
  272. * GroupTotals, if any.
  273. * @property totals
  274. * @type {GroupTotals}
  275. */
  276. this.totals = null;
  277. /**
  278. * Rows that are part of the group.
  279. * @property rows
  280. * @type {Array}
  281. */
  282. this.rows = [];
  283. /**
  284. * Sub-groups that are part of the group.
  285. * @property groups
  286. * @type {Array}
  287. */
  288. this.groups = null;
  289. /**
  290. * A unique key used to identify the group. This key can be used in calls to DataView
  291. * collapseGroup() or expandGroup().
  292. * @property groupingKey
  293. * @type {Object}
  294. */
  295. this.groupingKey = null;
  296. }
  297. Group.prototype = new NonDataItem();
  298. /***
  299. * Compares two Group instances.
  300. * @method equals
  301. * @return {Boolean}
  302. * @param group {Group} Group instance to compare to.
  303. */
  304. Group.prototype.equals = function (group) {
  305. return this.value === group.value &&
  306. this.count === group.count &&
  307. this.collapsed === group.collapsed;
  308. };
  309. /***
  310. * Information about group totals.
  311. * An instance of GroupTotals will be created for each totals row and passed to the aggregators
  312. * so that they can store arbitrary data in it. That data can later be accessed by group totals
  313. * formatters during the display.
  314. * @class GroupTotals
  315. * @extends Slick.NonDataItem
  316. * @constructor
  317. */
  318. function GroupTotals() {
  319. this.__groupTotals = true;
  320. /***
  321. * Parent Group.
  322. * @param group
  323. * @type {Group}
  324. */
  325. this.group = null;
  326. }
  327. GroupTotals.prototype = new NonDataItem();
  328. /***
  329. * A locking helper to track the active edit controller and ensure that only a single controller
  330. * can be active at a time. This prevents a whole class of state and validation synchronization
  331. * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress
  332. * and attempt a commit or cancel before proceeding.
  333. * @class EditorLock
  334. * @constructor
  335. */
  336. function EditorLock() {
  337. var activeEditController = null;
  338. /***
  339. * Returns true if a specified edit controller is active (has the edit lock).
  340. * If the parameter is not specified, returns true if any edit controller is active.
  341. * @method isActive
  342. * @param editController {EditController}
  343. * @return {Boolean}
  344. */
  345. this.isActive = function (editController) {
  346. return (editController ? activeEditController === editController : activeEditController !== null);
  347. };
  348. /***
  349. * Sets the specified edit controller as the active edit controller (acquire edit lock).
  350. * If another edit controller is already active, and exception will be thrown.
  351. * @method activate
  352. * @param editController {EditController} edit controller acquiring the lock
  353. */
  354. this.activate = function (editController) {
  355. if (editController === activeEditController) { // already activated?
  356. return;
  357. }
  358. if (activeEditController !== null) {
  359. throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
  360. }
  361. if (!editController.commitCurrentEdit) {
  362. throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
  363. }
  364. if (!editController.cancelCurrentEdit) {
  365. throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
  366. }
  367. activeEditController = editController;
  368. };
  369. /***
  370. * Unsets the specified edit controller as the active edit controller (release edit lock).
  371. * If the specified edit controller is not the active one, an exception will be thrown.
  372. * @method deactivate
  373. * @param editController {EditController} edit controller releasing the lock
  374. */
  375. this.deactivate = function (editController) {
  376. if (activeEditController !== editController) {
  377. throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
  378. }
  379. activeEditController = null;
  380. };
  381. /***
  382. * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
  383. * controller and returns whether the commit attempt was successful (commit may fail due to validation
  384. * errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
  385. * and false otherwise. If no edit controller is active, returns true.
  386. * @method commitCurrentEdit
  387. * @return {Boolean}
  388. */
  389. this.commitCurrentEdit = function () {
  390. return (activeEditController ? activeEditController.commitCurrentEdit() : true);
  391. };
  392. /***
  393. * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
  394. * controller and returns whether the edit was successfully cancelled. If no edit controller is
  395. * active, returns true.
  396. * @method cancelCurrentEdit
  397. * @return {Boolean}
  398. */
  399. this.cancelCurrentEdit = function cancelCurrentEdit() {
  400. return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
  401. };
  402. }
  403. })(jQuery);