PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/auiplugin/src/main/resources/experimental/js/atlassian/restfultable/restfultable.js

https://bitbucket.org/atlassian/aui
JavaScript | 931 lines | 584 code | 142 blank | 205 comment | 68 complexity | 4ea17d968613aa29552b16be2e7d0c81 MD5 | raw file
Possible License(s): Apache-2.0, JSON, LGPL-2.0
  1. (function ($) {
  2. /**
  3. * A table who's entries/rows are can be retrieved, added and updated via rest (CRUD).
  4. * It uses backbone.js to sync the tables state back to the server and vice versa, avoiding page refreshes.
  5. *
  6. * @class RestfulTable
  7. */
  8. AJS.RestfulTable = Backbone.View.extend({
  9. /**
  10. * @constructor
  11. * @param {Object} options
  12. * ... {String} id - The id for the table. This id will be used to fire events specific to this instance.
  13. * ... {boolean} allowEdit - Is the table editable. If true, clicking row will switch it to edit state.
  14. * ... {boolean} allowDelete - Can entries be removed from the table.
  15. * ... {boolean} allowCreate - Can new entries be added to the table.
  16. * ... {String} createPosition - If set to "bottom", place the create form at the bottom of the table instead of the top.
  17. * ... {String} addPosition - If set to "bottom", add new rows at the bottom of the table instead of the top.
  18. * If undefined the createPosition will be used to define where to add the new.
  19. * ... {boolean} allowReorder - Can we drag rows to reorder them.
  20. * ... {String} noEntriesMsg - Message that will be displayed under the table header if it is empty.
  21. * ... {Array} entries - initial data set to be rendered. Each item in the array will be used to create a new instance of options.model.
  22. * ... {AJS.RestfulTable.EntryModel} model - backbone model representing a row.
  23. * ... {Object} views - old option, don't reveal in API/docs
  24. * ... ... {AJS.RestfulTable.EditRow} editRow - Backbone view that renders the edit & create row. Your view MUST extend AJS.RestfulTable.EditRow.
  25. * ... ... {AJS.RestfulTable.Row} row - Backbone view that renders the readonly row. Your view MUST extend AJS.RestfulTable.Row.
  26. */
  27. initialize: function (options) {
  28. var instance = this;
  29. // combine default and user options
  30. instance.options = $.extend(true, instance._getDefaultOptions(options), options);
  31. // Prefix events for this instance with this id.
  32. instance.id = this.options.id;
  33. // faster lookup
  34. instance._events = AJS.RestfulTable.Events;
  35. instance.classNames = AJS.RestfulTable.ClassNames;
  36. instance.dataKeys = AJS.RestfulTable.DataKeys;
  37. // shortcuts to popular elements
  38. this.$table = $(options.el)
  39. .addClass(this.classNames.RESTFUL_TABLE)
  40. .addClass(this.classNames.ALLOW_HOVER)
  41. .addClass("aui")
  42. .addClass(instance.classNames.LOADING);
  43. this.$table.wrapAll("<form class='aui' action='#' />");
  44. this.$thead = $("<thead/>");
  45. this.$theadRow = $("<tr />").appendTo(this.$thead);
  46. this.$tbody = $("<tbody/>");
  47. if (!this.$table.length) {
  48. throw new Error("AJS.RestfulTable: Init failed! The table you have specified [" + this.$table.selector + "] cannot be found.")
  49. }
  50. if (!this.options.columns) {
  51. throw new Error("AJS.RestfulTable: Init failed! You haven't provided any columns to render.")
  52. }
  53. // Let user know the table is loading
  54. this.showGlobalLoading();
  55. $.each(this.options.columns, function (i, column) {
  56. var header = $.isFunction(column.header) ? column.header() : column.header;
  57. if (typeof header === "undefined") {
  58. console.warn("You have not specified [header] for column [" + column.id + "]. Using id for now...");
  59. header = column.id;
  60. }
  61. instance.$theadRow.append("<th>" + header + "</th>");
  62. });
  63. // columns for submit buttons and loading indicator used when editing
  64. instance.$theadRow.append("<th></th><th></th>");
  65. // create a new Backbone collection to represent rows (http://documentcloud.github.com/backbone/#Collection)
  66. this._models = this._createCollection();
  67. // shortcut to the class we use to create rows
  68. this._rowClass = this.options.views.row;
  69. this.editRows = []; // keep track of rows that are being edited concurrently
  70. this.$table.closest("form").submit(function (e) {
  71. if (instance.focusedRow) {
  72. // Delegates saving of row. See AJS.RestfulTable.EditRow.submit
  73. instance.focusedRow.trigger(instance._events.SAVE);
  74. }
  75. e.preventDefault();
  76. });
  77. if (this.options.allowReorder) {
  78. // Add allowance for another cell to the thead
  79. this.$theadRow.prepend("<th />");
  80. // Allow drag and drop reordering of rows
  81. this.$tbody.sortable({
  82. handle: "." +this.classNames.DRAG_HANDLE,
  83. helper: function(e, elt) {
  84. var helper = elt.clone(true).addClass(instance.classNames.MOVEABLE);
  85. helper.children().each(function (i) {
  86. $(this).width(elt.children().eq(i).width());
  87. });
  88. return helper;
  89. },
  90. start: function (event, ui) {
  91. var $this = ui.placeholder.find("td");
  92. // Make sure that when we start dragging widths do not change
  93. ui.item
  94. .addClass(instance.classNames.MOVEABLE)
  95. .children().each(function (i) {
  96. $(this).width($this.eq(i).width());
  97. });
  98. // Add a <td> to the placeholder <tr> to inherit CSS styles.
  99. ui.placeholder
  100. .html('<td colspan="' + instance.getColumnCount() + '">&nbsp;</td>')
  101. .css("visibility", "visible");
  102. // Stop hover effects etc from occuring as we move the mouse (while dragging) over other rows
  103. instance.getRowFromElement(ui.item[0]).trigger(instance._events.MODAL);
  104. },
  105. stop: function (event, ui) {
  106. if (AJS.$(ui.item[0]).is(":visible")) {
  107. ui.item
  108. .removeClass(instance.classNames.MOVEABLE)
  109. .children().attr("style", "");
  110. ui.placeholder.removeClass(instance.classNames.ROW);
  111. // Return table to a normal state
  112. instance.getRowFromElement(ui.item[0]).trigger(instance._events.MODELESS);
  113. }
  114. },
  115. update: function (event, ui) {
  116. var nextModel,
  117. nextRow,
  118. data = {},
  119. row = instance.getRowFromElement(ui.item[0]);
  120. if (row) {
  121. if (instance.options.reverseOrder) {
  122. // Everything is backwards here because on the client we are in reverse order.
  123. nextRow = ui.item.next();
  124. if (!nextRow.length) {
  125. data.position = "First";
  126. } else {
  127. nextModel = instance.getRowFromElement(nextRow).model;
  128. data.after = nextModel.url();
  129. }
  130. } else {
  131. nextRow = ui.item.prev();
  132. if (!nextRow.length) {
  133. data.position = "First";
  134. } else {
  135. nextModel = instance.getRowFromElement(nextRow).model;
  136. data.after = nextModel.url();
  137. }
  138. }
  139. $.ajax({
  140. url: row.model.url() + "/move",
  141. type: "POST",
  142. dataType: "json",
  143. contentType: "application/json",
  144. data: JSON.stringify(data),
  145. complete: function () {
  146. // hides loading indicator (spinner)
  147. row.hideLoading();
  148. },
  149. success: function (xhr) {
  150. AJS.triggerEvtForInst(instance._events.REORDER_SUCCESS, instance, [xhr]);
  151. },
  152. error: function (xhr) {
  153. var responseData = $.parseJSON(xhr.responseText || xhr.data);
  154. AJS.triggerEvtForInst(instance._events.SERVER_ERROR, instance, [responseData, xhr, this]);
  155. }
  156. });
  157. // shows loading indicator (spinner)
  158. row.showLoading();
  159. }
  160. },
  161. axis: "y",
  162. delay: 0,
  163. containment: "document",
  164. cursor: "move",
  165. scroll: true,
  166. zIndex: 8000
  167. });
  168. // Prevent text selection while reordering.
  169. this.$tbody.bind("selectstart mousedown", function (event) {
  170. return !$(event.target).is("." + instance.classNames.DRAG_HANDLE);
  171. });
  172. }
  173. if (this.options.allowCreate !== false) {
  174. // Create row responsible for adding new entries ...
  175. this._createRow = new this.options.views.editRow({
  176. columns: this.options.columns,
  177. isCreateRow: true,
  178. model: this.options.model.extend({
  179. url: function () {
  180. return instance.options.resources.self;
  181. }
  182. }),
  183. cancelAccessKey: this.options.cancelAccessKey,
  184. submitAccessKey: this.options.submitAccessKey,
  185. allowReorder: this.options.allowReorder
  186. })
  187. .bind(this._events.CREATED, function (values) {
  188. if ((instance.options.addPosition == undefined && instance.options.createPosition === "bottom")
  189. || instance.options.addPosition === "bottom") {
  190. instance.addRow(values);
  191. } else {
  192. instance.addRow(values, 0);
  193. }
  194. })
  195. .bind(this._events.VALIDATION_ERROR, function () {
  196. this.trigger(instance._events.FOCUS);
  197. })
  198. .render({
  199. errors: {},
  200. values: {}
  201. });
  202. // ... and appends it as the first row
  203. this.$create = $('<tbody class="' + this.classNames.CREATE + '" />')
  204. .append(this._createRow.el);
  205. // Manage which row has focus
  206. this._applyFocusCoordinator(this._createRow);
  207. // focus create row
  208. this._createRow.trigger(this._events.FOCUS);
  209. }
  210. // when a model is removed from the collection, remove it from the viewport also
  211. this._models.bind("remove", function (model) {
  212. $.each(instance.getRows(), function (i, row) {
  213. if (row.model === model) {
  214. if (row.hasFocus() && instance._createRow) {
  215. instance._createRow.trigger(instance._events.FOCUS);
  216. }
  217. instance.removeRow(row);
  218. }
  219. });
  220. });
  221. if ($.isFunction(this.options.resources.all)) {
  222. this.options.resources.all(function (entries) {
  223. instance.populate(entries);
  224. });
  225. } else {
  226. $.get(this.options.resources.all, function (entries) {
  227. instance.populate(entries);
  228. });
  229. }
  230. },
  231. _createCollection: function() {
  232. var instance = this;
  233. // create a new Backbone collection to represent rows (http://documentcloud.github.com/backbone/#Collection)
  234. var rowsAwareCollection = this.options.Collection.extend({
  235. // Force the collection to re-sort itself. You don't need to call this under normal
  236. // circumstances, as the set will maintain sort order as each item is added.
  237. sort:function (options) {
  238. options || (options = {});
  239. if (!this.comparator) {
  240. throw new Error('Cannot sort a set without a comparator');
  241. }
  242. this.tableRows = instance.getRows();
  243. this.models = this.sortBy(this.comparator);
  244. this.tableRows = undefined;
  245. if (!options.silent) {
  246. this.trigger('refresh', this, options);
  247. }
  248. return this;
  249. },
  250. remove:function (models, options) {
  251. this.tableRows = instance.getRows();
  252. Backbone.Collection.prototype.remove.apply(this, arguments);
  253. this.tableRows = undefined;
  254. return this;
  255. }
  256. });
  257. return new rowsAwareCollection([], {
  258. comparator:function (row) {
  259. // sort models in collection based on dom ordering
  260. var index;
  261. $.each(this.tableRows !== undefined ? this.tableRows : instance.getRows(), function (i) {
  262. if (this.model.id === row.id) {
  263. index = i;
  264. return false;
  265. }
  266. });
  267. return index;
  268. }
  269. });
  270. },
  271. /**
  272. * Refreshes table with entries
  273. *
  274. * @param entries
  275. */
  276. populate: function (entries) {
  277. if (this.options.reverseOrder) {
  278. entries.reverse();
  279. }
  280. this.hideGlobalLoading();
  281. if (entries && entries.length) {
  282. // Empty the models collection
  283. this._models.reset([], { silent: true });
  284. // Add all the entries to collection and render them
  285. this.renderRows(entries);
  286. // show message to user if we have no entries
  287. if (this.isEmpty()) {
  288. this.showNoEntriesMsg();
  289. }
  290. } else {
  291. this.showNoEntriesMsg();
  292. }
  293. // Ok, lets let everyone know that we are done...
  294. this.$table
  295. .append(this.$thead);
  296. if (this.options.createPosition === "bottom") {
  297. this.$table.append(this.$tbody)
  298. .append(this.$create);
  299. } else {
  300. this.$table
  301. .append(this.$create)
  302. .append(this.$tbody);
  303. }
  304. this.$table.removeClass(this.classNames.LOADING)
  305. .trigger(this._events.INITIALIZED, [this]);
  306. AJS.triggerEvtForInst(this._events.INITIALIZED, this, [this]);
  307. if (this.options.autoFocus) {
  308. this.$table.find(":input:text:first").focus(); // set focus to first field
  309. }
  310. },
  311. /**
  312. * Shows loading indicator and text
  313. *
  314. * @return {AJS.RestfulTable}
  315. */
  316. showGlobalLoading: function () {
  317. if (!this.$loading) {
  318. this.$loading = $('<div class="aui-restfultable-init"><span class="aui-restfultable-throbber">' +
  319. '</span><span class="aui-restfultable-loading">' + this.options.loadingMsg + '</span></div>');
  320. }
  321. if (!this.$loading.is(":visible")) {
  322. this.$loading.insertAfter(this.$table);
  323. }
  324. return this
  325. },
  326. /**
  327. * Hides loading indicator and text
  328. * @return {AJS.RestfulTable}
  329. */
  330. hideGlobalLoading: function () {
  331. if (this.$loading) {
  332. this.$loading.remove();
  333. }
  334. return this;
  335. },
  336. /**
  337. * Adds row to collection and renders it
  338. *
  339. * @param {Object} values
  340. * @param {number} index
  341. * @return {AJS.RestfulTable}
  342. */
  343. addRow: function (values, index) {
  344. var view,
  345. model;
  346. if (!values.id) {
  347. throw new Error("AJS.RestfulTable.addRow: to add a row values object must contain an id. "
  348. + "Maybe you are not returning it from your restend point?"
  349. + "Recieved:" + JSON.stringify(values));
  350. }
  351. model = new this.options.model(values);
  352. view = this._renderRow(model, index);
  353. this._models.add(model);
  354. this.removeNoEntriesMsg();
  355. // Let everyone know we added a row
  356. AJS.triggerEvtForInst(this._events.ROW_ADDED, this, [view, this]);
  357. return this;
  358. },
  359. /**
  360. * Provided a view, removes it from display and backbone collection
  361. *
  362. * @param {AJS.RestfulTable.Row}
  363. */
  364. removeRow: function (row) {
  365. this._models.remove(row.model);
  366. row.remove();
  367. if (this.isEmpty()) {
  368. this.showNoEntriesMsg();
  369. }
  370. // Let everyone know we removed a row
  371. AJS.triggerEvtForInst(this._events.ROW_REMOVED, this, [row, this]);
  372. },
  373. /**
  374. * Is there any entries in the table
  375. *
  376. * @return {Boolean}
  377. */
  378. isEmpty: function () {
  379. return this._models.length === 0;
  380. },
  381. /**
  382. * Gets all models
  383. *
  384. * @return {Backbone.Collection}
  385. */
  386. getModels: function () {
  387. return this._models;
  388. },
  389. /**
  390. * Gets table body
  391. *
  392. * @return {jQuery}
  393. */
  394. getTable: function () {
  395. return this.$table;
  396. },
  397. /**
  398. * Gets table body
  399. *
  400. * @return {jQuery}
  401. */
  402. getTableBody: function () {
  403. return this.$tbody;
  404. },
  405. /**
  406. * Gets create Row
  407. *
  408. * @return {B
  409. */
  410. getCreateRow: function () {
  411. return this._createRow;
  412. },
  413. /**
  414. * Gets the number of table colums
  415. *
  416. * @return {Number}
  417. */
  418. getColumnCount: function () {
  419. return this.options.columns.length + 2; // plus 2 accounts for the columns allocated to submit buttons and loading indicator
  420. },
  421. /**
  422. * Get the AJS.RestfulTable.Row that corresponds to the given <tr> element.
  423. *
  424. * @param {HTMLElement} tr
  425. * @return {?AJS.RestfulTable.Row}
  426. */
  427. getRowFromElement: function (tr) {
  428. return $(tr).data(this.dataKeys.ROW_VIEW);
  429. },
  430. /**
  431. * Shows message {options.noEntriesMsg} to the user if there are no entries
  432. *
  433. * @return {AJS.RestfulTable}
  434. */
  435. showNoEntriesMsg: function () {
  436. if (this.$noEntries) {
  437. this.$noEntries.remove();
  438. }
  439. this.$noEntries = $("<tr>")
  440. .addClass(this.classNames.NO_ENTRIES)
  441. .append($("<td>")
  442. .attr("colspan", this.getColumnCount())
  443. .text(this.options.noEntriesMsg)
  444. )
  445. .appendTo(this.$tbody);
  446. return this;
  447. },
  448. /**
  449. * Removes message {options.noEntriesMsg} to the user if there ARE entries
  450. *
  451. * @return {AJS.RestfulTable}
  452. */
  453. removeNoEntriesMsg: function () {
  454. if (this.$noEntries && this._models.length > 0) {
  455. this.$noEntries.remove();
  456. }
  457. return this;
  458. },
  459. /**
  460. * Gets the AJS.RestfulTable.Row from their associated <tr> elements
  461. *
  462. * @return {Array<AJS.RestfulTable.Row>}
  463. */
  464. getRows: function () {
  465. var instance = this,
  466. views = [];
  467. this.$tbody.find("." + this.classNames.READ_ONLY).each(function () {
  468. var $row = $(this),
  469. view = $row.data(instance.dataKeys.ROW_VIEW);
  470. if (view) {
  471. views.push(view);
  472. }
  473. });
  474. return views;
  475. },
  476. /**
  477. * Appends entry to end or specified index of table
  478. *
  479. * @param {AJS.RestfulTable.EntryModel} model
  480. * @param index
  481. * @return {jQuery}
  482. */
  483. _renderRow: function (model, index) {
  484. var instance = this,
  485. $rows = this.$tbody.find("." + this.classNames.READ_ONLY),
  486. $row,
  487. view;
  488. view = new this._rowClass({
  489. model: model,
  490. columns: this.options.columns,
  491. allowEdit: this.options.allowEdit,
  492. allowDelete: this.options.allowDelete,
  493. allowReorder: this.options.allowReorder,
  494. deleteConfirmation: this.options.deleteConfirmation
  495. });
  496. this.removeNoEntriesMsg();
  497. view.bind(this._events.EDIT_ROW, function (field) {
  498. instance.edit(this, field);
  499. });
  500. $row = view.render().$el;
  501. if (index !== -1) {
  502. if (typeof index === "number" && $rows.length !== 0) {
  503. $row.insertBefore($rows[index]);
  504. } else {
  505. this.$tbody.append($row);
  506. }
  507. }
  508. $row.data(this.dataKeys.ROW_VIEW, view);
  509. // deactivate all rows - used in the cases, such as opening a dropdown where you do not want the table editable
  510. // or any interactions
  511. view.bind(this._events.MODAL, function () {
  512. instance.$table.removeClass(instance.classNames.ALLOW_HOVER);
  513. instance.$tbody.sortable("disable");
  514. $.each(instance.getRows(), function () {
  515. if (!instance.isRowBeingEdited(this)) {
  516. this.delegateEvents({}); // clear all events
  517. }
  518. });
  519. });
  520. view.bind(this._events.ANIMATION_STARTED, function () {
  521. instance.$table.removeClass(instance.classNames.ALLOW_HOVER);
  522. });
  523. view.bind(this._events.ANIMATION_FINISHED, function () {
  524. instance.$table.addClass(instance.classNames.ALLOW_HOVER);
  525. });
  526. // activate all rows - used in the cases, such as opening a dropdown where you do not want the table editable
  527. // or any interactions
  528. view.bind(this._events.MODELESS, function () {
  529. instance.$table.addClass(instance.classNames.ALLOW_HOVER);
  530. instance.$tbody.sortable("enable");
  531. $.each(instance.getRows(), function () {
  532. if (!instance.isRowBeingEdited(this)) {
  533. this.delegateEvents(); // rebind all events
  534. }
  535. });
  536. });
  537. // ensure that when this row is focused no other are
  538. this._applyFocusCoordinator(view);
  539. this.trigger(this._events.ROW_INITIALIZED, view);
  540. return view;
  541. },
  542. /**
  543. * Returns if the row is edit mode or note
  544. *
  545. * @param {AJS.RestfulTable.Row} - read onyl row to check if being edited
  546. * @return {Boolean}
  547. */
  548. isRowBeingEdited: function (row) {
  549. var isBeingEdited = false;
  550. $.each(this.editRows, function () {
  551. if (this.el === row.el) {
  552. isBeingEdited = true;
  553. return false;
  554. }
  555. });
  556. return isBeingEdited;
  557. },
  558. /**
  559. * Ensures that when supplied view is focused no others are
  560. *
  561. * @param {Backbone.View} view
  562. * @return {AJS.RestfulTable}
  563. */
  564. _applyFocusCoordinator: function (view) {
  565. var instance = this;
  566. if (!view.hasFocusBound) {
  567. view.hasFocusBound = true;
  568. view.bind(this._events.FOCUS, function () {
  569. if (instance.focusedRow && instance.focusedRow !== view) {
  570. instance.focusedRow.trigger(instance._events.BLUR);
  571. }
  572. instance.focusedRow = view;
  573. if (view instanceof AJS.RestfulTable.Row && instance._createRow) {
  574. instance._createRow.enable();
  575. }
  576. });
  577. }
  578. return this;
  579. },
  580. /**
  581. * Remove specificed row from collection holding rows being concurrently edited
  582. *
  583. * @param {AJS.RestfulTable.EditRow} editView
  584. * @return {AJS.RestfulTable}
  585. */
  586. _removeEditRow: function (editView) {
  587. var index = $.inArray(editView, this.editRows);
  588. this.editRows.splice(index, 1);
  589. return this;
  590. },
  591. /**
  592. * Focuses last row still being edited or create row (if it exists)
  593. *
  594. * @return {AJS.RestfulTable}
  595. */
  596. _shiftFocusAfterEdit: function () {
  597. if (this.editRows.length > 0) {
  598. this.editRows[this.editRows.length-1].trigger(this._events.FOCUS);
  599. } else if (this._createRow) {
  600. this._createRow.trigger(this._events.FOCUS);
  601. }
  602. return this;
  603. },
  604. /**
  605. * Evaluate if we save row when we blur. We can only do this when there is one row being edited at a time, otherwise
  606. * it causes an infinate loop JRADEV-5325
  607. *
  608. * @return {boolean}
  609. */
  610. _saveEditRowOnBlur: function () {
  611. return this.editRows.length <= 1;
  612. },
  613. /**
  614. * Dismisses rows being edited concurrently that have no changes
  615. */
  616. dismissEditRows: function () {
  617. var instance = this;
  618. $.each(this.editRows, function () {
  619. if (!this.hasUpdates()) {
  620. this.trigger(instance._events.FINISHED_EDITING);
  621. }
  622. });
  623. },
  624. /**
  625. * Converts readonly row to editable view
  626. *
  627. * @param {Backbone.View} row
  628. * @param {String} field - field name to focus
  629. * @return {Backbone.View} editRow
  630. */
  631. edit: function (row, field) {
  632. var instance = this,
  633. editRow = new this.options.views.editRow({
  634. el: row.el,
  635. columns: this.options.columns,
  636. isUpdateMode: true,
  637. allowReorder: this.options.allowReorder,
  638. model: row.model,
  639. cancelAccessKey: this.options.cancelAccessKey,
  640. submitAccessKey: this.options.submitAccessKey
  641. }),
  642. values = row.model.toJSON();
  643. values.update = true;
  644. editRow.render({
  645. errors: {},
  646. update: true,
  647. values: values
  648. })
  649. .bind(instance._events.UPDATED, function (model, focusUpdated) {
  650. instance._removeEditRow (this);
  651. this.unbind();
  652. row.render().delegateEvents(); // render and rebind events
  653. row.trigger(instance._events.UPDATED); // trigger blur fade out
  654. if (focusUpdated !== false) {
  655. instance._shiftFocusAfterEdit();
  656. }
  657. })
  658. .bind(instance._events.VALIDATION_ERROR, function () {
  659. this.trigger(instance._events.FOCUS);
  660. })
  661. .bind(instance._events.FINISHED_EDITING, function () {
  662. instance._removeEditRow(this);
  663. row.render().delegateEvents();
  664. this.unbind(); // avoid any other updating, blurring, finished editing, cancel events being fired
  665. })
  666. .bind(instance._events.CANCEL, function () {
  667. instance._removeEditRow(this);
  668. this.unbind(); // avoid any other updating, blurring, finished editing, cancel events being fired
  669. row.render().delegateEvents(); // render and rebind events
  670. instance._shiftFocusAfterEdit();
  671. })
  672. .bind(instance._events.BLUR, function () {
  673. instance.dismissEditRows(); // dismiss edit rows that have no changes
  674. if (instance._saveEditRowOnBlur()) {
  675. this.trigger(instance._events.SAVE, false); // save row, which if successful will call the updated event above
  676. }
  677. });
  678. // Ensure that if focus is pulled to another row, we blur the edit row
  679. this._applyFocusCoordinator(editRow);
  680. // focus edit row, which has the flow on effect of blurring current focused row
  681. editRow.trigger(instance._events.FOCUS, field);
  682. // disables form fields
  683. if (instance._createRow) {
  684. instance._createRow.disable();
  685. }
  686. this.editRows.push(editRow);
  687. return editRow;
  688. },
  689. /**
  690. * Renders all specified rows
  691. *
  692. * @param {Array} array of objects describing Backbone.Model's to render
  693. * @return {AJS.RestfulTable}
  694. */
  695. renderRows: function (rows) {
  696. var comparator = this._models.comparator, els = [];
  697. this._models.comparator = undefined; // disable temporarily, assume rows are sorted
  698. var models = _.map(rows, function(row) {
  699. var model = new this.options.model(row);
  700. els.push(this._renderRow(model, -1).el);
  701. return model;
  702. }, this);
  703. this._models.add(models, {silent:true});
  704. this._models.comparator = comparator;
  705. this.removeNoEntriesMsg();
  706. this.$tbody.append(els);
  707. return this;
  708. },
  709. /**
  710. * Gets default options
  711. *
  712. * @param {Object} options
  713. */
  714. _getDefaultOptions: function (options) {
  715. return {
  716. model: options.model || AJS.RestfulTable.EntryModel,
  717. allowEdit: true,
  718. views: {
  719. editRow: AJS.RestfulTable.EditRow,
  720. row: AJS.RestfulTable.Row
  721. },
  722. Collection: Backbone.Collection.extend({
  723. url: options.resources.self,
  724. model: options.model || AJS.RestfulTable.EntryModel
  725. }),
  726. allowReorder: false,
  727. loadingMsg: options.loadingMsg || AJS.I18n.getText("aui.words.loading")
  728. }
  729. }
  730. });
  731. // jQuery data keys (http://api.jquery.com/jQuery.data/)
  732. AJS.RestfulTable.DataKeys = {
  733. ENABLED_SUBMIT: "enabledSubmit",
  734. ROW_VIEW: "RestfulTable_Row_View"
  735. };
  736. // CSS style classes. DON'T hard code
  737. AJS.RestfulTable.ClassNames = {
  738. NO_VALUE: "aui-restfultable-editable-no-value",
  739. NO_ENTRIES: "aui-restfultable-no-entires",
  740. RESTFUL_TABLE: "aui-restfultable",
  741. ROW: "aui-restfultable-row",
  742. READ_ONLY: "aui-restfultable-readonly",
  743. ACTIVE: "aui-restfultable-active",
  744. ALLOW_HOVER: "aui-restfultable-allowhover",
  745. FOCUSED: "aui-restfultable-focused",
  746. MOVEABLE: "aui-restfultable-movable",
  747. ANIMATING: "aui-restfultable-animate",
  748. DISABLED: "aui-restfultable-disabled",
  749. SUBMIT: "aui-restfultable-submit",
  750. CANCEL: "aui-restfultable-cancel",
  751. EDIT_ROW: "aui-restfultable-editrow",
  752. CREATE: "aui-restfultable-create",
  753. DRAG_HANDLE: "aui-restfultable-draghandle",
  754. ORDER: "aui-restfultable-order",
  755. EDITABLE: "aui-restfultable-editable",
  756. ERROR: "error",
  757. DELETE: "aui-restfultable-delete",
  758. LOADING: "loading"
  759. };
  760. // Custom events
  761. AJS.RestfulTable.Events = {
  762. // AJS events
  763. REORDER_SUCCESS: "RestfulTable.reorderSuccess",
  764. ROW_ADDED: "RestfulTable.rowAdded",
  765. ROW_REMOVED: "RestfulTable.rowRemoved",
  766. EDIT_ROW: "RestfulTable.switchedToEditMode",
  767. SERVER_ERROR: "RestfulTable.serverError",
  768. // backbone events
  769. CREATED: "created",
  770. UPDATED: "updated",
  771. FOCUS: "focus",
  772. BLUR: "blur",
  773. SUBMIT: "submit",
  774. SAVE: "save",
  775. MODAL: "modal",
  776. MODELESS: "modeless",
  777. CANCEL: "cancel",
  778. CONTENT_REFRESHED: "contentRefreshed",
  779. RENDER: "render",
  780. FINISHED_EDITING: "finishedEditing",
  781. VALIDATION_ERROR: "validationError",
  782. SUBMIT_STARTED: "submitStarted",
  783. SUBMIT_FINISHED: "submitFinished",
  784. ANIMATION_STARTED: "animationStarted",
  785. ANIMATION_FINISHED: "animationFinisehd",
  786. INITIALIZED: "initialized",
  787. ROW_INITIALIZED: "rowInitialized"
  788. };
  789. })(AJS.$);