PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/web-app/lib/backgrid/backgrid.js

https://github.com/delfianto/rams-web-app
JavaScript | 2172 lines | 935 code | 285 blank | 952 comment | 175 complexity | 0831a992ca6f5a414fc56e8d13bb9a48 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. backgrid
  3. http://github.com/wyuenho/backgrid
  4. Copyright (c) 2013 Jimmy Yuen Ho Wong
  5. Licensed under the MIT @license.
  6. */
  7. (function (root, $, _, Backbone) {
  8. "use strict";
  9. /*
  10. backgrid
  11. http://github.com/wyuenho/backgrid
  12. Copyright (c) 2013 Jimmy Yuen Ho Wong
  13. Licensed under the MIT @license.
  14. */
  15. var window = root;
  16. var Backgrid = root.Backgrid = {
  17. VERSION: "0.1.4",
  18. Extension: {}
  19. };
  20. // Copyright 2009, 2010 Kristopher Michael Kowal
  21. // https://github.com/kriskowal/es5-shim
  22. // ES5 15.5.4.20
  23. // http://es5.github.com/#x15.5.4.20
  24. var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
  25. "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
  26. "\u2029\uFEFF";
  27. if (!String.prototype.trim || ws.trim()) {
  28. // http://blog.stevenlevithan.com/archives/faster-trim-javascript
  29. // http://perfectionkills.com/whitespace-deviations/
  30. ws = "[" + ws + "]";
  31. var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
  32. trimEndRegexp = new RegExp(ws + ws + "*$");
  33. String.prototype.trim = function trim() {
  34. if (this === undefined || this === null) {
  35. throw new TypeError("can't convert " + this + " to object");
  36. }
  37. return String(this)
  38. .replace(trimBeginRegexp, "")
  39. .replace(trimEndRegexp, "");
  40. };
  41. }
  42. function capitalize(s) {
  43. return String.fromCharCode(s.charCodeAt(0) - 32) + s.slice(1);
  44. }
  45. function lpad(str, length, padstr) {
  46. var paddingLen = length - (str + '').length;
  47. paddingLen = paddingLen < 0 ? 0 : paddingLen;
  48. var padding = '';
  49. for (var i = 0; i < paddingLen; i++) {
  50. padding = padding + padstr;
  51. }
  52. return padding + str;
  53. }
  54. function requireOptions(options, requireOptionKeys) {
  55. for (var i = 0; i < requireOptionKeys.length; i++) {
  56. var key = requireOptionKeys[i];
  57. if (_.isUndefined(options[key])) {
  58. throw new TypeError("'" + key + "' is required");
  59. }
  60. }
  61. }
  62. function resolveNameToClass(name, suffix) {
  63. if (_.isString(name)) {
  64. var key = capitalize(name) + suffix;
  65. var klass = Backgrid[key] || Backgrid.Extension[key];
  66. if (_.isUndefined(klass)) {
  67. throw new ReferenceError("Class '" + key + "' not found");
  68. }
  69. return klass;
  70. }
  71. return name;
  72. }
  73. /*
  74. backgrid
  75. http://github.com/wyuenho/backgrid
  76. Copyright (c) 2013 Jimmy Yuen Ho Wong
  77. Licensed under the MIT @license.
  78. */
  79. /**
  80. Just a convenient class for interested parties to subclass.
  81. The default Cell classes don't require the formatter to be a subclass of
  82. Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods
  83. are defined.
  84. @abstract
  85. @class Backgrid.CellFormatter
  86. @constructor
  87. */
  88. var CellFormatter = Backgrid.CellFormatter = function () {
  89. };
  90. _.extend(CellFormatter.prototype, {
  91. /**
  92. Takes a raw value from a model and returns a formatted string for display.
  93. @member Backgrid.CellFormatter
  94. @param {*} rawData
  95. @return {string}
  96. */
  97. fromRaw: function (rawData) {
  98. return rawData;
  99. },
  100. /**
  101. Takes a formatted string, usually from user input, and returns a
  102. appropriately typed value for persistence in the model.
  103. If the user input is invalid or unable to be converted to a raw value
  104. suitable for persistence in the model, toRaw must return `undefined`.
  105. @member Backgrid.CellFormatter
  106. @param {string} formattedData
  107. @return {*|undefined}
  108. */
  109. toRaw: function (formattedData) {
  110. return formattedData;
  111. }
  112. });
  113. /**
  114. A floating point number formatter. Doesn't understand notation at the moment.
  115. @class Backgrid.NumberFormatter
  116. @extends Backgrid.CellFormatter
  117. @constructor
  118. @throws {RangeError} If decimals < 0 or > 20.
  119. */
  120. var NumberFormatter = Backgrid.NumberFormatter = function (options) {
  121. options = options ? _.clone(options) : {};
  122. _.extend(this, this.defaults, options);
  123. if (this.decimals < 0 || this.decimals > 20) {
  124. throw new RangeError("decimals must be between 0 and 20");
  125. }
  126. };
  127. NumberFormatter.prototype = new CellFormatter;
  128. _.extend(NumberFormatter.prototype, {
  129. /**
  130. @member Backgrid.NumberFormatter
  131. @cfg {Object} options
  132. @cfg {number} [options.decimals=2] Number of decimals to display. Must be an integer.
  133. @cfg {string} [options.decimalSeparator='.'] The separator to use when
  134. displaying decimals.
  135. @cfg {string} [options.orderSeparator=','] The separator to use to
  136. separator thousands. May be an empty string.
  137. */
  138. defaults: {
  139. decimals: 2,
  140. decimalSeparator: '.',
  141. orderSeparator: ','
  142. },
  143. HUMANIZED_NUM_RE: /(\d)(?=(?:\d{3})+$)/g,
  144. /**
  145. Takes a floating point number and convert it to a formatted string where
  146. every thousand is separated by `orderSeparator`, with a `decimal` number of
  147. decimals separated by `decimalSeparator`. The number returned is rounded
  148. the usual way.
  149. @member Backgrid.NumberFormatter
  150. @param {number} number
  151. @return {string}
  152. */
  153. fromRaw: function (number) {
  154. if (isNaN(number) || number === null) return '';
  155. number = number.toFixed(~~this.decimals);
  156. var parts = number.split('.');
  157. var integerPart = parts[0];
  158. var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : '';
  159. return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart;
  160. },
  161. /**
  162. Takes a string, possibly formatted with `orderSeparator` and/or
  163. `decimalSeparator`, and convert it back to a number.
  164. @member Backgrid.NumberFormatter
  165. @param {string} formattedData
  166. @return {number|undefined} Undefined if the string cannot be converted to
  167. a number.
  168. */
  169. toRaw: function (formattedData) {
  170. var rawData = '';
  171. var thousands = formattedData.trim().split(this.orderSeparator);
  172. for (var i = 0; i < thousands.length; i++) {
  173. rawData += thousands[i];
  174. }
  175. var decimalParts = rawData.split(this.decimalSeparator);
  176. rawData = '';
  177. for (var i = 0; i < decimalParts.length; i++) {
  178. rawData = rawData + decimalParts[i] + '.';
  179. }
  180. if (rawData[rawData.length - 1] === '.') {
  181. rawData = rawData.slice(0, rawData.length - 1);
  182. }
  183. var result = (rawData * 1).toFixed(~~this.decimals) * 1;
  184. if (_.isNumber(result) && !_.isNaN(result)) return result;
  185. }
  186. });
  187. /**
  188. Formatter to converts between various datetime string formats.
  189. This class only understands ISO-8601 formatted datetime strings. See
  190. Backgrid.Extension.MomentFormatter if you need a much more flexible datetime
  191. formatter.
  192. @class Backgrid.DatetimeFormatter
  193. @extends Backgrid.CellFormatter
  194. @constructor
  195. @throws {Error} If both `includeDate` and `includeTime` are false.
  196. */
  197. var DatetimeFormatter = Backgrid.DatetimeFormatter = function (options) {
  198. options = options ? _.clone(options) : {};
  199. _.extend(this, this.defaults, options);
  200. if (!this.includeDate && !this.includeTime) {
  201. throw new Error("Either includeDate or includeTime must be true");
  202. }
  203. };
  204. DatetimeFormatter.prototype = new CellFormatter;
  205. _.extend(DatetimeFormatter.prototype, {
  206. /**
  207. @member Backgrid.DatetimeFormatter
  208. @cfg {Object} options
  209. @cfg {boolean} [options.includeDate=true] Whether the values include the
  210. date part.
  211. @cfg {boolean} [options.includeTime=true] Whether the values include the
  212. time part.
  213. @cfg {boolean} [options.includeMilli=false] If `includeTime` is true,
  214. whether to include the millisecond part, if it exists.
  215. */
  216. defaults: {
  217. includeDate: true,
  218. includeTime: true,
  219. includeMilli: false
  220. },
  221. DATE_RE: /^([+\-]?\d{4})-(\d{2})-(\d{2})$/,
  222. TIME_RE: /^(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?$/,
  223. ISO_SPLITTER_RE: /T|Z| +/,
  224. _convert: function (data, validate) {
  225. if (_.isNull(data) || _.isUndefined(data)) return data;
  226. data = data.trim();
  227. var parts = data.split(this.ISO_SPLITTER_RE) || [];
  228. var date = this.DATE_RE.test(parts[0]) ? parts[0] : '';
  229. var time = date && parts[1] ? parts[1] : this.TIME_RE.test(parts[0]) ? parts[0] : '';
  230. var YYYYMMDD = this.DATE_RE.exec(date) || [];
  231. var HHmmssSSS = this.TIME_RE.exec(time) || [];
  232. if (validate) {
  233. if (this.includeDate && _.isUndefined(YYYYMMDD[0])) return;
  234. if (this.includeTime && _.isUndefined(HHmmssSSS[0])) return;
  235. if (!this.includeDate && date) return;
  236. if (!this.includeTime && time) return;
  237. }
  238. var jsDate = new Date(Date.UTC(YYYYMMDD[1] * 1 || 0,
  239. YYYYMMDD[2] * 1 - 1 || 0,
  240. YYYYMMDD[3] * 1 || 0,
  241. HHmmssSSS[1] * 1 || null,
  242. HHmmssSSS[2] * 1 || null,
  243. HHmmssSSS[3] * 1 || null,
  244. HHmmssSSS[5] * 1 || null));
  245. var result = '';
  246. if (this.includeDate) {
  247. result = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
  248. }
  249. if (this.includeTime) {
  250. result = result + (this.includeDate ? 'T' : '') + lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
  251. if (this.includeMilli) {
  252. result = result + '.' + lpad(jsDate.getUTCMilliseconds(), 3, 0);
  253. }
  254. }
  255. if (this.includeDate && this.includeTime) {
  256. result += "Z";
  257. }
  258. return result;
  259. },
  260. /**
  261. Converts an ISO-8601 formatted datetime string to a datetime string, date
  262. string or a time string. The timezone is ignored if supplied.
  263. @member Backgrid.DatetimeFormatter
  264. @param {string} rawData
  265. @return {string|null|undefined} ISO-8601 string in UTC. Null and undefined values are returned as is.
  266. */
  267. fromRaw: function (rawData) {
  268. return this._convert(rawData);
  269. },
  270. /**
  271. Converts an ISO-8601 formatted datetime string to a datetime string, date
  272. string or a time string. The timezone is ignored if supplied. This method
  273. parses the input values exactly the same way as
  274. Backgrid.Extension.MomentFormatter#fromRaw(), in addition to doing some
  275. sanity checks.
  276. @member Backgrid.DatetimeFormatter
  277. @param {string} formattedData
  278. @return {string|undefined} ISO-8601 string in UTC. Undefined if a date is
  279. found `includeDate` is false, or a time is found if `includeTime` is false,
  280. or if `includeDate` is true and a date is not found, or if `includeTime` is
  281. true and a time is not found.
  282. */
  283. toRaw: function (formattedData) {
  284. return this._convert(formattedData, true);
  285. }
  286. });
  287. /*
  288. backgrid
  289. http://github.com/wyuenho/backgrid
  290. Copyright (c) 2013 Jimmy Yuen Ho Wong
  291. Licensed under the MIT @license.
  292. */
  293. /**
  294. Generic cell editor base class. Only defines an initializer for a number of
  295. required parameters.
  296. @abstract
  297. @class Backgrid.CellEditor
  298. @extends Backbone.View
  299. */
  300. var CellEditor = Backgrid.CellEditor = Backbone.View.extend({
  301. /**
  302. Initializer.
  303. @param {Object} options
  304. @param {*} options.parent
  305. @param {Backgrid.CellFormatter} options.formatter
  306. @param {Backgrid.Column} options.column
  307. @param {Backbone.Model} options.model
  308. @throws {TypeError} If `formatter` is not a formatter instance, or when
  309. `model` or `column` are undefined.
  310. */
  311. initialize: function (options) {
  312. requireOptions(options, ["formatter", "column", "model"]);
  313. this.parent = options.parent;
  314. this.formatter = options.formatter;
  315. this.column = options.column;
  316. if (!(this.column instanceof Column)) {
  317. this.column = new Column(this.column);
  318. }
  319. if (this.parent && _.isFunction(this.parent.on)) {
  320. this.listenTo(this.parent, "editing", this.postRender);
  321. }
  322. },
  323. /**
  324. Post-rendering setup and initialization. Focuses the cell editor's `el` in
  325. this default implementation. **Should** be called by Cell classes after
  326. calling Backgrid.CellEditor#render.
  327. */
  328. postRender: function () {
  329. this.$el.focus();
  330. return this;
  331. }
  332. });
  333. /**
  334. InputCellEditor the cell editor type used by most core cell types. This cell
  335. editor renders a text input box as its editor. The input will render a
  336. placeholder if the value is empty on supported browsers.
  337. @class Backgrid.InputCellEditor
  338. @extends Backgrid.CellEditor
  339. */
  340. var InputCellEditor = Backgrid.InputCellEditor = CellEditor.extend({
  341. /** @property */
  342. tagName: "input",
  343. /** @property */
  344. attributes: {
  345. type: "text"
  346. },
  347. /** @property */
  348. events: {
  349. "blur": "saveOrCancel",
  350. "keydown": "saveOrCancel"
  351. },
  352. /**
  353. Initializer. Removes this `el` from the DOM when a `done` event is
  354. triggered.
  355. @param {Object} options
  356. @param {Backgrid.CellFormatter} options.formatter
  357. @param {Backgrid.Column} options.column
  358. @param {Backbone.Model} options.model
  359. @param {string} [options.placeholder]
  360. */
  361. initialize: function (options) {
  362. CellEditor.prototype.initialize.apply(this, arguments);
  363. if (options.placeholder) {
  364. this.$el.attr("placeholder", options.placeholder);
  365. }
  366. this.listenTo(this, "done", this.remove);
  367. },
  368. /**
  369. Renders a text input with the cell value formatted for display, if it
  370. exists.
  371. */
  372. render: function () {
  373. this.$el.val(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
  374. return this;
  375. },
  376. /**
  377. If the key pressed is `enter` or `tab`, converts the value in the editor to
  378. a raw value for the model using the formatter.
  379. If the key pressed is `esc` the changes are undone.
  380. If the editor's value was changed and goes out of focus (`blur`), the event
  381. is intercepted, cancelled so the cell remains in focus pending for further
  382. action.
  383. Triggers a Backbone `done` event when successful. `error` if the value
  384. cannot be converted. Classes listening to the `error` event, usually the
  385. Cell classes, should respond appropriately, usually by rendering some kind
  386. of error feedback.
  387. @param {Event} e
  388. */
  389. saveOrCancel: function (e) {
  390. if (e.type === "keydown") {
  391. // enter or tab
  392. if (e.keyCode === 13 || e.keyCode === 9) {
  393. e.preventDefault();
  394. var valueToSet = this.formatter.toRaw(this.$el.val());
  395. if (_.isUndefined(valueToSet) || !this.model.set(this.column.get("name"), valueToSet,
  396. {validate: true})) {
  397. this.trigger("error");
  398. }
  399. else {
  400. this.trigger("done");
  401. }
  402. }
  403. // esc
  404. else if (e.keyCode === 27) {
  405. // undo
  406. e.stopPropagation();
  407. this.trigger("done");
  408. }
  409. }
  410. else if (e.type === "blur") {
  411. if (this.formatter.fromRaw(this.model.get(this.column.get("name"))) === this.$el.val()) {
  412. this.trigger("done");
  413. }
  414. else {
  415. var self = this;
  416. var timeout = window.setTimeout(function () {
  417. self.$el.focus();
  418. window.clearTimeout(timeout);
  419. }, 1);
  420. }
  421. }
  422. },
  423. postRender: function () {
  424. // move the cursor to the end on firefox if text is right aligned
  425. if (this.$el.css("text-align") === "right") {
  426. var val = this.$el.val();
  427. this.$el.focus().val(null).val(val);
  428. }
  429. else {
  430. this.$el.focus();
  431. }
  432. return this;
  433. }
  434. });
  435. /**
  436. The super-class for all Cell types. By default, this class renders a plain
  437. table cell with the model value converted to a string using the
  438. formatter. The table cell is clickable, upon which the cell will go into
  439. editor mode, which is rendered by a Backgrid.InputCellEditor instance by
  440. default. Upon any formatting errors, this class will add a `error` CSS class
  441. to the table cell.
  442. @abstract
  443. @class Backgrid.Cell
  444. @extends Backbone.View
  445. */
  446. var Cell = Backgrid.Cell = Backbone.View.extend({
  447. /** @property */
  448. tagName: "td",
  449. /**
  450. @property {Backgrid.CellFormatter|Object|string} [formatter=new CellFormatter()]
  451. */
  452. formatter: new CellFormatter(),
  453. /**
  454. @property {Backgrid.CellEditor} [editor=Backgrid.InputCellEditor] The
  455. default editor for all cell instances of this class. This value must be a
  456. class, it will be automatically instantiated upon entering edit mode.
  457. See Backgrid.CellEditor
  458. */
  459. editor: InputCellEditor,
  460. /** @property */
  461. events: {
  462. "click": "enterEditMode"
  463. },
  464. /**
  465. Initializer.
  466. @param {Object} options
  467. @param {Backbone.Model} options.model
  468. @param {Backgrid.Column} options.column
  469. @throws {ReferenceError} If formatter is a string but a formatter class of
  470. said name cannot be found in the Backgrid module.
  471. */
  472. initialize: function (options) {
  473. requireOptions(options, ["model", "column"]);
  474. this.column = options.column;
  475. if (!(this.column instanceof Column)) {
  476. this.column = new Column(this.column);
  477. }
  478. this.formatter = resolveNameToClass(this.formatter, "Formatter");
  479. this.editor = resolveNameToClass(this.editor, "CellEditor");
  480. this.listenTo(this.model, "change:" + this.column.get("name"), function () {
  481. if (!this.$el.hasClass("editor")) this.render();
  482. });
  483. },
  484. /**
  485. Render a text string in a table cell. The text is converted from the
  486. model's raw value for this cell's column.
  487. */
  488. render: function () {
  489. this.$el.empty().text(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
  490. return this;
  491. },
  492. /**
  493. If this column is editable, a new CellEditor instance is instantiated with
  494. its required parameters and listens on the editor's `done` and `error`
  495. events. When the editor is `done`, edit mode is exited. When the editor
  496. triggers an `error` event, it means the editor is unable to convert the
  497. current user input to an apprpriate value for the model's column. An
  498. `editor` CSS class is added to the cell upon entering edit mode.
  499. */
  500. enterEditMode: function (e) {
  501. if (this.column.get("editable")) {
  502. this.currentEditor = new this.editor({
  503. parent: this,
  504. column: this.column,
  505. model: this.model,
  506. formatter: this.formatter
  507. });
  508. /**
  509. Backbone Event. Fired when a cell is entering edit mode and an editor
  510. instance has been constructed, but before it is rendered and inserted
  511. into the DOM.
  512. @event edit
  513. @param {Backgrid.Cell} cell This cell instance.
  514. @param {Backgrid.CellEditor} editor The cell editor constructed.
  515. */
  516. this.trigger("edit", this, this.currentEditor);
  517. this.listenTo(this.currentEditor, "done", this.exitEditMode);
  518. this.listenTo(this.currentEditor, "error", this.renderError);
  519. this.$el.empty();
  520. this.undelegateEvents();
  521. this.$el.append(this.currentEditor.$el);
  522. this.currentEditor.render();
  523. this.$el.addClass("editor");
  524. /**
  525. Backbone Event. Fired when a cell has finished switching to edit mode.
  526. @event editing
  527. @param {Backgrid.Cell} cell This cell instance.
  528. @param {Backgrid.CellEditor} editor The cell editor constructed.
  529. */
  530. this.trigger("editing", this, this.currentEditor);
  531. }
  532. },
  533. /**
  534. Put an `error` CSS class on the table cell.
  535. */
  536. renderError: function () {
  537. this.$el.addClass("error");
  538. },
  539. /**
  540. Removes the editor and re-render in display mode.
  541. */
  542. exitEditMode: function () {
  543. this.$el.removeClass("error");
  544. this.currentEditor.off(null, null, this);
  545. this.currentEditor.remove();
  546. delete this.currentEditor;
  547. this.$el.removeClass("editor");
  548. this.render();
  549. this.delegateEvents();
  550. },
  551. /**
  552. Clean up this cell.
  553. @chainable
  554. */
  555. remove: function () {
  556. if (this.currentEditor) {
  557. this.currentEditor.remove.apply(this, arguments);
  558. delete this.currentEditor;
  559. }
  560. return Backbone.View.prototype.remove.apply(this, arguments);
  561. }
  562. });
  563. /**
  564. StringCell displays HTML escaped strings and accepts anything typed in.
  565. @class Backgrid.StringCell
  566. @extends Backgrid.Cell
  567. */
  568. var StringCell = Backgrid.StringCell = Cell.extend({
  569. /** @property */
  570. className: "string-cell"
  571. // No formatter needed. Strings call auto-escaped by jQuery on insertion.
  572. });
  573. /**
  574. UriCell renders an HTML `<a>` anchor for the value and accepts URIs as user
  575. input values. A URI input is URI encoded using `encodeURI()` before writing
  576. to the underlying model.
  577. @class Backgrid.UriCell
  578. @extends Backgrid.Cell
  579. */
  580. var UriCell = Backgrid.UriCell = Cell.extend({
  581. /** @property */
  582. className: "uri-cell",
  583. formatter: {
  584. fromRaw: function (rawData) {
  585. return rawData;
  586. },
  587. toRaw: function (formattedData) {
  588. var result = encodeURI(formattedData);
  589. return result === "undefined" ? undefined : result;
  590. }
  591. },
  592. render: function () {
  593. this.$el.empty();
  594. var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
  595. this.$el.append($("<a>", {
  596. href: formattedValue,
  597. title: formattedValue,
  598. target: "_blank"
  599. }).text(formattedValue));
  600. return this;
  601. }
  602. });
  603. /**
  604. Like Backgrid.UriCell, EmailCell renders an HTML `<a>` anchor for the
  605. value. The `href` in the anchor is prefixed with `mailto:`. EmailCell will
  606. complain if the user enters a string that doesn't contain the `@` sign.
  607. @class Backgrid.EmailCell
  608. @extends Backgrid.Cell
  609. */
  610. var EmailCell = Backgrid.EmailCell = Cell.extend({
  611. /** @property */
  612. className: "email-cell",
  613. formatter: {
  614. fromRaw: function (rawData) {
  615. return rawData;
  616. },
  617. toRaw: function (formattedData) {
  618. var parts = formattedData.split("@");
  619. if (parts.length === 2 && _.all(parts)) {
  620. return formattedData;
  621. }
  622. }
  623. },
  624. render: function () {
  625. this.$el.empty();
  626. var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
  627. this.$el.append($("<a>", {
  628. href: "mailto:" + formattedValue,
  629. title: formattedValue
  630. }).text(formattedValue));
  631. return this;
  632. }
  633. });
  634. /**
  635. NumberCell is a generic cell that renders all numbers. Numbers are formatted
  636. using a Backgrid.NumberFormatter.
  637. @class Backgrid.NumberCell
  638. @extends Backgrid.Cell
  639. */
  640. var NumberCell = Backgrid.NumberCell = Cell.extend({
  641. /** @property */
  642. className: "number-cell",
  643. /**
  644. @property {number} [decimals=2] Must be an integer.
  645. */
  646. decimals: NumberFormatter.prototype.defaults.decimals,
  647. /** @property {string} [decimalSeparator='.'] */
  648. decimalSeparator: NumberFormatter.prototype.defaults.decimalSeparator,
  649. /** @property {string} [orderSeparator=','] */
  650. orderSeparator: NumberFormatter.prototype.defaults.orderSeparator,
  651. /** @property {Backgrid.CellFormatter} [formatter=Backgrid.NumberFormatter] */
  652. formatter: NumberFormatter,
  653. /**
  654. Initializes this cell and the number formatter.
  655. @param {Object} options
  656. @param {Backbone.Model} options.model
  657. @param {Backgrid.Column} options.column
  658. */
  659. initialize: function (options) {
  660. Cell.prototype.initialize.apply(this, arguments);
  661. this.formatter = new this.formatter({
  662. decimals: this.decimals,
  663. decimalSeparator: this.decimalSeparator,
  664. orderSeparator: this.orderSeparator
  665. });
  666. }
  667. });
  668. /**
  669. An IntegerCell is just a Backgrid.NumberCell with 0 decimals. If a floating point
  670. number is supplied, the number is simply rounded the usual way when
  671. displayed.
  672. @class Backgrid.IntegerCell
  673. @extends Backgrid.NumberCell
  674. */
  675. var IntegerCell = Backgrid.IntegerCell = NumberCell.extend({
  676. /** @property */
  677. className: "integer-cell",
  678. /**
  679. @property {number} decimals Must be an integer.
  680. */
  681. decimals: 0
  682. });
  683. /**
  684. DatetimeCell is a basic cell that accepts datetime string values in RFC-2822
  685. or W3C's subset of ISO-8601 and displays them in ISO-8601 format. For a much
  686. more sophisticated date time cell with better datetime formatting, take a
  687. look at the Backgrid.Extension.MomentCell extension.
  688. @class Backgrid.DatetimeCell
  689. @extends Backgrid.Cell
  690. See:
  691. - Backgrid.Extension.MomentCell
  692. - Backgrid.DatetimeFormatter
  693. */
  694. var DatetimeCell = Backgrid.DatetimeCell = Cell.extend({
  695. /** @property */
  696. className: "datetime-cell",
  697. /**
  698. @property {boolean} [includeDate=true]
  699. */
  700. includeDate: DatetimeFormatter.prototype.defaults.includeDate,
  701. /**
  702. @property {boolean} [includeTime=true]
  703. */
  704. includeTime: DatetimeFormatter.prototype.defaults.includeTime,
  705. /**
  706. @property {boolean} [includeMilli=false]
  707. */
  708. includeMilli: DatetimeFormatter.prototype.defaults.includeMilli,
  709. /** @property {Backgrid.CellFormatter} [formatter=Backgrid.DatetimeFormatter] */
  710. formatter: DatetimeFormatter,
  711. /**
  712. Initializes this cell and the datetime formatter.
  713. @param {Object} options
  714. @param {Backbone.Model} options.model
  715. @param {Backgrid.Column} options.column
  716. */
  717. initialize: function (options) {
  718. Cell.prototype.initialize.apply(this, arguments);
  719. this.formatter = new this.formatter({
  720. includeDate: this.includeDate,
  721. includeTime: this.includeTime,
  722. includeMilli: this.includeMilli
  723. });
  724. var placeholder = this.includeDate ? "YYYY-MM-DD" : "";
  725. placeholder += (this.includeDate && this.includeTime) ? "T" : "";
  726. placeholder += this.includeTime ? "HH:mm:ss" : "";
  727. placeholder += (this.includeTime && this.includeMilli) ? ".SSS" : "";
  728. this.editor = this.editor.extend({
  729. attributes: _.extend({}, this.editor.prototype.attributes, this.editor.attributes, {
  730. placeholder: placeholder
  731. })
  732. });
  733. }
  734. });
  735. /**
  736. DateCell is a Backgrid.DatetimeCell without the time part.
  737. @class Backgrid.DateCell
  738. @extends Backgrid.DatetimeCell
  739. */
  740. var DateCell = Backgrid.DateCell = DatetimeCell.extend({
  741. /** @property */
  742. className: "date-cell",
  743. /** @property */
  744. includeTime: false
  745. });
  746. /**
  747. TimeCell is a Backgrid.DatetimeCell without the date part.
  748. @class Backgrid.TimeCell
  749. @extends Backgrid.DatetimeCell
  750. */
  751. var TimeCell = Backgrid.TimeCell = DatetimeCell.extend({
  752. /** @property */
  753. className: "time-cell",
  754. /** @property */
  755. includeDate: false
  756. });
  757. /**
  758. BooleanCell is a different kind of cell in that there's no difference between
  759. display mode and edit mode and this cell type always renders a checkbox for
  760. selection.
  761. @class Backgrid.BooleanCell
  762. @extends Backgrid.Cell
  763. */
  764. var BooleanCell = Backgrid.BooleanCell = Cell.extend({
  765. /** @property */
  766. className: "boolean-cell",
  767. /**
  768. BooleanCell simple uses a default HTML checkbox template instead of a
  769. CellEditor instance.
  770. @property {function(Object, ?Object=): string} editor The Underscore.js template to
  771. render the editor.
  772. */
  773. editor: _.template("<input type='checkbox'<%= checked ? checked='checked' : '' %> />'"),
  774. /**
  775. Since the editor is not an instance of a CellEditor subclass, more things
  776. need to be done in BooleanCell class to listen to editor mode events.
  777. */
  778. events: {
  779. "click": "enterEditMode",
  780. "blur input[type=checkbox]": "exitEditMode",
  781. "change input[type=checkbox]": "save"
  782. },
  783. /**
  784. Renders a checkbox and check it if the model value of this column is true,
  785. uncheck otherwise.
  786. */
  787. render: function () {
  788. this.$el.empty();
  789. this.currentEditor = $(this.editor({
  790. checked: this.formatter.fromRaw(this.model.get(this.column.get("name")))
  791. }));
  792. this.$el.append(this.currentEditor);
  793. return this;
  794. },
  795. /**
  796. Simple focuses the checkbox and add an `editor` CSS class to the cell.
  797. */
  798. enterEditMode: function (e) {
  799. this.$el.addClass("editor");
  800. this.currentEditor.focus();
  801. },
  802. /**
  803. Removed the `editor` CSS class from the cell.
  804. */
  805. exitEditMode: function (e) {
  806. this.$el.removeClass("editor");
  807. },
  808. /**
  809. Set true to the model attribute if the checkbox is checked, false
  810. otherwise.
  811. */
  812. save: function (e) {
  813. var val = this.formatter.toRaw(this.currentEditor.prop("checked"));
  814. this.model.set(this.column.get("name"), val);
  815. }
  816. });
  817. /**
  818. SelectCellEditor renders an HTML `<select>` fragment as the editor.
  819. @class Backgrid.SelectCellEditor
  820. @extends Backgrid.CellEditor
  821. */
  822. var SelectCellEditor = Backgrid.SelectCellEditor = CellEditor.extend({
  823. /** @property */
  824. tagName: "select",
  825. /** @property */
  826. events: {
  827. "change": "save",
  828. "blur": "save"
  829. },
  830. /** @property {function(Object, ?Object=): string} template */
  831. template: _.template('<option value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- text %></option>'),
  832. setOptionValues: function (optionValues) {
  833. this.optionValues = optionValues;
  834. },
  835. _renderOptions: function (nvps, currentValue) {
  836. var options = '';
  837. for (var i = 0; i < nvps.length; i++) {
  838. options = options + this.template({
  839. text: nvps[i][0],
  840. value: nvps[i][1],
  841. selected: currentValue == nvps[i][1]
  842. });
  843. }
  844. return options;
  845. },
  846. /**
  847. Renders the options if `optionValues` is a list of name-value pairs. The
  848. options are contained inside option groups if `optionValues` is a list of
  849. object hashes. The name is rendered at the option text and the value is the
  850. option value. If `optionValues` is a function, it is called without a
  851. parameter.
  852. */
  853. render: function () {
  854. this.$el.empty();
  855. var optionValues = _.result(this, "optionValues");
  856. var currentValue = this.model.get(this.column.get("name"));
  857. if (!_.isArray(optionValues)) throw TypeError("optionValues must be an array");
  858. var optionValue = null;
  859. var optionText = null;
  860. var optionValue = null;
  861. var optgroupName = null;
  862. var optgroup = null;
  863. for (var i = 0; i < optionValues.length; i++) {
  864. var optionValue = optionValues[i];
  865. if (_.isArray(optionValue)) {
  866. optionText = optionValue[0];
  867. optionValue = optionValue[1];
  868. this.$el.append(this.template({
  869. text: optionText,
  870. value: optionValue,
  871. selected: optionValue == currentValue
  872. }));
  873. }
  874. else if (_.isObject(optionValue)) {
  875. optgroupName = optionValue.name;
  876. optgroup = $("<optgroup></optgroup>", { label: optgroupName });
  877. optgroup.append(this._renderOptions(optionValue.values, currentValue));
  878. this.$el.append(optgroup);
  879. }
  880. else {
  881. throw TypeError("optionValues elements must be a name-value pair or an object hash of { name: 'optgroup label', value: [option name-value pairs] }");
  882. }
  883. }
  884. return this;
  885. },
  886. /**
  887. Saves the value of the selected option to the model attribute. Triggers a
  888. `done` Backbone event.
  889. */
  890. save: function (e) {
  891. this.model.set(this.column.get("name"), this.formatter.toRaw(this.$el.val()));
  892. this.trigger("done");
  893. }
  894. });
  895. /**
  896. SelectCell is also a different kind of cell in that upon going into edit mode
  897. the cell renders a list of options for to pick from, as opposed to an input
  898. box.
  899. SelectCell cannot be referenced by its string name when used in a column
  900. definition because requires an `optionValues` class attribute to be
  901. defined. `optionValues` can either be a list of name-value pairs, to be
  902. rendered as options, or a list of object hashes which consist of a key *name*
  903. which is the option group name, and a key *values* which is a list of
  904. name-value pairs to be rendered as options under that option group.
  905. In addition, `optionValues` can also be a parameter-less function that
  906. returns one of the above. If the options are static, it is recommended the
  907. returned values to be memoized. _.memoize() is a good function to help with
  908. that.
  909. @class Backgrid.SelectCell
  910. @extends Backgrid.Cell
  911. */
  912. var SelectCell = Backgrid.SelectCell = Cell.extend({
  913. /** @property */
  914. className: "select-cell",
  915. /** @property */
  916. editor: SelectCellEditor,
  917. /**
  918. @property {Array.<Array>|Array.<{name: string, values: Array.<Array>}>} optionValues
  919. */
  920. optionValues: undefined,
  921. /**
  922. Initializer.
  923. @param {Object} options
  924. @param {Backbone.Model} options.model
  925. @param {Backgrid.Column} options.column
  926. @throws {TypeError} If `optionsValues` is undefined.
  927. */
  928. initialize: function (options) {
  929. Cell.prototype.initialize.apply(this, arguments);
  930. requireOptions(this, ["optionValues"]);
  931. this.optionValues = _.result(this, "optionValues");
  932. this.listenTo(this, "edit", this.setOptionValues);
  933. },
  934. setOptionValues: function (cell, editor) {
  935. editor.setOptionValues(this.optionValues);
  936. },
  937. /**
  938. Renders the label using the raw value as key to look up from `optionValues`.
  939. @throws {TypeError} If `optionValues` is malformed.
  940. */
  941. render: function () {
  942. this.$el.empty();
  943. var optionValues = this.optionValues;
  944. var rawData = this.formatter.fromRaw(this.model.get(this.column.get("name")));
  945. try {
  946. if (!_.isArray(optionValues) || _.isEmpty(optionValues)) throw new TypeError;
  947. for (var i = 0; i < optionValues.length; i++) {
  948. var optionValue = optionValues[i];
  949. if (_.isArray(optionValue)) {
  950. var optionText = optionValue[0];
  951. var optionValue = optionValue[1];
  952. if (optionValue == rawData) {
  953. this.$el.append(optionText);
  954. break;
  955. }
  956. }
  957. else if (_.isObject(optionValue)) {
  958. var optionGroupValues = optionValue.values;
  959. for (var j = 0; j < optionGroupValues.length; j++) {
  960. var optionGroupValue = optionGroupValues[j];
  961. if (optionGroupValue[1] == rawData) {
  962. this.$el.append(optionGroupValue[0]);
  963. break;
  964. }
  965. }
  966. }
  967. else {
  968. throw new TypeError;
  969. }
  970. }
  971. }
  972. catch (ex) {
  973. if (ex instanceof TypeError) {
  974. throw TypeError("'optionValues' must be of type {Array.<Array>|Array.<{name: string, values: Array.<Array>}>}");
  975. }
  976. throw ex;
  977. }
  978. return this;
  979. }
  980. });
  981. /*
  982. backgrid
  983. http://github.com/wyuenho/backgrid
  984. Copyright (c) 2013 Jimmy Yuen Ho Wong
  985. Licensed under the MIT @license.
  986. */
  987. /**
  988. A Column is a placeholder for column metadata.
  989. You usually don't need to create an instance of this class yourself as a
  990. collection of column instances will be created for you from a list of column
  991. attributes in the Backgrid.js view class constructors.
  992. @class Backgrid.Column
  993. @extends Backbone.Model
  994. */
  995. var Column = Backgrid.Column = Backbone.Model.extend({
  996. defaults: {
  997. name: undefined,
  998. label: undefined,
  999. sortable: true,
  1000. editable: true,
  1001. renderable: true,
  1002. formatter: undefined,
  1003. cell: undefined,
  1004. headerCell: undefined
  1005. },
  1006. /**
  1007. Initializes this Column instance.
  1008. @param {Object} attrs Column attributes.
  1009. @param {string} attrs.name The name of the model attribute.
  1010. @param {string|Backgrid.Cell} attrs.cell The cell type.
  1011. If this is a string, the capitalized form will be used to look up a
  1012. cell class in Backbone, i.e.: string => StringCell. If a Cell subclass
  1013. is supplied, it is initialized with a hash of parameters. If a Cell
  1014. instance is supplied, it is used directly.
  1015. @param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
  1016. @param {string} [attrs.label] The label to show in the header.
  1017. @param {boolean} [attrs.sortable=true]
  1018. @param {boolean} [attrs.editable=true]
  1019. @param {boolean} [attrs.renderable=true]
  1020. @param {Backgrid.CellFormatter|Object|string} [attrs.formatter] The
  1021. formatter to use to convert between raw model values and user input.
  1022. @throws {TypeError} If attrs.cell or attrs.options are not supplied.
  1023. @throws {ReferenceError} If attrs.cell is a string but a cell class of
  1024. said name cannot be found in the Backgrid module.
  1025. See:
  1026. - Backgrid.Cell
  1027. - Backgrid.CellFormatter
  1028. */
  1029. initialize: function (attrs) {
  1030. requireOptions(attrs, ["cell", "name"]);
  1031. if (!this.has("label")) {
  1032. this.set({ label: this.get("name") }, { silent: true });
  1033. }
  1034. var cell = resolveNameToClass(this.get("cell"), "Cell");
  1035. this.set({ cell: cell }, { silent: true });
  1036. }
  1037. });
  1038. /**
  1039. A Backbone collection of Column instances.
  1040. @class Backgrid.Columns
  1041. @extends Backbone.Collection
  1042. */
  1043. var Columns = Backgrid.Columns = Backbone.Collection.extend({
  1044. /**
  1045. @property {Backgrid.Column} model
  1046. */
  1047. model: Column
  1048. });
  1049. /*
  1050. backgrid
  1051. http://github.com/wyuenho/backgrid
  1052. Copyright (c) 2013 Jimmy Yuen Ho Wong
  1053. Licensed under the MIT @license.
  1054. */
  1055. /**
  1056. Row is a simple container view that takes a model instance and a list of
  1057. column metadata describing how each of the model's attribute is to be
  1058. rendered, and apply the appropriate cell to each attribute.
  1059. @class Backgrid.Row
  1060. @extends Backbone.View
  1061. */
  1062. var Row = Backgrid.Row = Backbone.View.extend({
  1063. /** @property */
  1064. tagName: "tr",
  1065. initOptionRequires: ["columns", "model"],
  1066. /**
  1067. Initializes a row view instance.
  1068. @param {Object} options
  1069. @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
  1070. @param {Backbone.Model} options.model The model instance to render.
  1071. @throws {TypeError} If options.columns or options.model is undefined.
  1072. */
  1073. initialize: function (options) {
  1074. requireOptions(options, this.initOptionRequires);
  1075. var columns = this.columns = options.columns;
  1076. if (!(columns instanceof Backbone.Collection)) {
  1077. columns = this.columns = new Columns(columns);
  1078. }
  1079. var cells = this.cells = [];
  1080. for (var i = 0; i < columns.length; i++) {
  1081. cells.push(this.makeCell(columns.at(i), options));
  1082. }
  1083. this.listenTo(columns, "change:renderable", function (column, renderable) {
  1084. for (var i = 0; i < cells.length; i++) {
  1085. var cell = cells[i];
  1086. if (cell.column.get("name") == column.get("name")) {
  1087. if (renderable) cell.$el.show(); else cell.$el.hide();
  1088. }
  1089. }
  1090. });
  1091. this.listenTo(columns, "add", function (column, columns) {
  1092. var i = columns.indexOf(column);
  1093. var cell = this.makeCell(column, options);
  1094. cells.splice(i, 0, cell);
  1095. if (!cell.column.get("renderable")) cell.$el.hide();
  1096. var $el = this.$el;
  1097. if (i === 0) {
  1098. $el.prepend(cell.render().$el);
  1099. }
  1100. else if (i === columns.length - 1) {
  1101. $el.append(cell.render().$el);
  1102. }
  1103. else {
  1104. $el.children().eq(i).before(cell.render().$el);
  1105. }
  1106. });
  1107. this.listenTo(columns, "remove", function (column, columns, opts) {
  1108. cells[opts.index].remove();
  1109. cells.splice(opts.index, 1);
  1110. });
  1111. },
  1112. /**
  1113. Factory method for making a cell. Used by #initialize internally. Override
  1114. this to provide an appropriate cell instance for a custom Row subclass.
  1115. @protected
  1116. @param {Backgrid.Column} column
  1117. @param {Object} options The options passed to #initialize.
  1118. @return {Backgrid.Cell}
  1119. */
  1120. makeCell: function (column) {
  1121. return new (column.get("cell"))({
  1122. column: column,
  1123. model: this.model
  1124. });
  1125. },
  1126. /**
  1127. Renders a row of cells for this row's model.
  1128. */
  1129. render: function () {
  1130. this.$el.empty();
  1131. var fragment = document.createDocumentFragment();
  1132. for (var i = 0; i < this.cells.length; i++) {
  1133. var cell = this.cells[i];
  1134. fragment.appendChild(cell.render().el);
  1135. if (!cell.column.get("renderable")) cell.$el.hide();
  1136. }
  1137. this.el.appendChild(fragment);
  1138. return this;
  1139. },
  1140. /**
  1141. Clean up this row and its cells.
  1142. @chainable
  1143. */
  1144. remove: function () {
  1145. for (var i = 0; i < this.cells.length; i++) {
  1146. var cell = this.cells[i];
  1147. cell.remove.apply(cell, arguments);
  1148. }
  1149. return Backbone.View.prototype.remove.apply(this, arguments);
  1150. }
  1151. });
  1152. /*
  1153. backgrid
  1154. http://github.com/wyuenho/backgrid
  1155. Copyright (c) 2013 Jimmy Yuen Ho Wong
  1156. Licensed under the MIT @license.
  1157. */
  1158. /**
  1159. HeaderCell is a special cell class that renders a column header cell. If the
  1160. column is sortable, a sorter is also rendered and will trigger a table
  1161. refresh after sorting.
  1162. @class Backgrid.HeaderCell
  1163. @extends Backbone.View
  1164. */
  1165. var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
  1166. /** @property */
  1167. tagName: "th",
  1168. /** @property */
  1169. events: {
  1170. "click a": "onClick"
  1171. },
  1172. /**
  1173. @property {null|"ascending"|"descending"} _direction The current sorting
  1174. direction of this column.
  1175. */
  1176. _direction: null,
  1177. /**
  1178. Initializer.
  1179. @param {Object} options
  1180. @param {Backgrid.Column|Object} options.column
  1181. @throws {TypeError} If options.column or options.collection is undefined.
  1182. */
  1183. initialize: function (options) {
  1184. requireOptions(options, ["column", "collection"]);
  1185. this.column = options.column;
  1186. if (!(this.column instanceof Column)) {
  1187. this.column = new Column(this.column);
  1188. }
  1189. this.listenTo(Backbone, "backgrid:sort", this._resetCellDirection);
  1190. },
  1191. /**
  1192. Gets or sets the direction of this cell. If called directly without
  1193. parameters, returns the current direction of this cell, otherwise sets
  1194. it. If a `null` is given, sets this cell back to the default order.
  1195. @param {null|"ascending"|"descending"} dir
  1196. @return {null|string} The current direction or the changed direction.
  1197. */
  1198. direction: function (dir) {
  1199. if (arguments.length) {
  1200. if (this._direction) this.$el.removeClass(this._direction);
  1201. if (dir) this.$el.addClass(dir);
  1202. this._direction = dir;
  1203. }
  1204. return this._direction;
  1205. },
  1206. /**
  1207. Event handler for the Backbone `backgrid:sort` event. Resets this cell's
  1208. direction to default if sorting is being done on another column.
  1209. @private
  1210. */
  1211. _resetCellDirection: function (sortByColName, direction, comparator, collection) {
  1212. if (collection == this.collection) {
  1213. if (sortByColName !== this.column.get("name")) this.direction(null);
  1214. else this.direction(direction);
  1215. }
  1216. },
  1217. /**
  1218. Event handler for the `click` event on the cell's anchor. If the column is
  1219. sortable, clicking on the anchor will cycle through 3 sorting orderings -
  1220. `ascending`, `descending`, and default.
  1221. */
  1222. onClick: function (e) {
  1223. e.preventDefault();
  1224. var columnName = this.column.get("name");
  1225. if (this.column.get("sortable")) {
  1226. if (this.direction() === "ascending") {
  1227. this.sort(columnName, "descending", function (left, right) {
  1228. var leftVal = lef

Large files files are truncated, but you can click here to view the full file