PageRenderTime 62ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/zephor/aui-archive
JavaScript | 937 lines | 590 code | 142 blank | 205 comment | 68 complexity | a8fc5ff7b315fc6fbbfc626aaef5690c MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, JSON, Apache-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.ROW_EDIT, function (field) {
  498. AJS.triggerEvtForInst(this._events.EDIT_ROW, {}, [this, instance]);
  499. instance.edit(this, field);
  500. });
  501. $row = view.render().$el;
  502. if (index !== -1) {
  503. if (typeof index === "number" && $rows.length !== 0) {
  504. $row.insertBefore($rows[index]);
  505. } else {
  506. this.$tbody.append($row);
  507. }
  508. }
  509. $row.data(this.dataKeys.ROW_VIEW, view);
  510. // deactivate all rows - used in the cases, such as opening a dropdown where you do not want the table editable
  511. // or any interactions
  512. view.bind(this._events.MODAL, function () {
  513. instance.$table.removeClass(instance.classNames.ALLOW_HOVER);
  514. instance.$tbody.sortable("disable");
  515. $.each(instance.getRows(), function () {
  516. if (!instance.isRowBeingEdited(this)) {
  517. this.delegateEvents({}); // clear all events
  518. }
  519. });
  520. });
  521. view.bind(this._events.ANIMATION_STARTED, function () {
  522. instance.$table.removeClass(instance.classNames.ALLOW_HOVER);
  523. });
  524. view.bind(this._events.ANIMATION_FINISHED, function () {
  525. instance.$table.addClass(instance.classNames.ALLOW_HOVER);
  526. });
  527. // activate all rows - used in the cases, such as opening a dropdown where you do not want the table editable
  528. // or any interactions
  529. view.bind(this._events.MODELESS, function () {
  530. instance.$table.addClass(instance.classNames.ALLOW_HOVER);
  531. instance.$tbody.sortable("enable");
  532. $.each(instance.getRows(), function () {
  533. if (!instance.isRowBeingEdited(this)) {
  534. this.delegateEvents(); // rebind all events
  535. }
  536. });
  537. });
  538. // ensure that when this row is focused no other are
  539. this._applyFocusCoordinator(view);
  540. this.trigger(this._events.ROW_INITIALIZED, view);
  541. return view;
  542. },
  543. /**
  544. * Returns if the row is edit mode or note
  545. *
  546. * @param {AJS.RestfulTable.Row} - read onyl row to check if being edited
  547. * @return {Boolean}
  548. */
  549. isRowBeingEdited: function (row) {
  550. var isBeingEdited = false;
  551. $.each(this.editRows, function () {
  552. if (this.el === row.el) {
  553. isBeingEdited = true;
  554. return false;
  555. }
  556. });
  557. return isBeingEdited;
  558. },
  559. /**
  560. * Ensures that when supplied view is focused no others are
  561. *
  562. * @param {Backbone.View} view
  563. * @return {AJS.RestfulTable}
  564. */
  565. _applyFocusCoordinator: function (view) {
  566. var instance = this;
  567. if (!view.hasFocusBound) {
  568. view.hasFocusBound = true;
  569. view.bind(this._events.FOCUS, function () {
  570. if (instance.focusedRow && instance.focusedRow !== view) {
  571. instance.focusedRow.trigger(instance._events.BLUR);
  572. }
  573. instance.focusedRow = view;
  574. if (view instanceof AJS.RestfulTable.Row && instance._createRow) {
  575. instance._createRow.enable();
  576. }
  577. });
  578. }
  579. return this;
  580. },
  581. /**
  582. * Remove specificed row from collection holding rows being concurrently edited
  583. *
  584. * @param {AJS.RestfulTable.EditRow} editView
  585. * @return {AJS.RestfulTable}
  586. */
  587. _removeEditRow: function (editView) {
  588. var index = $.inArray(editView, this.editRows);
  589. this.editRows.splice(index, 1);
  590. return this;
  591. },
  592. /**
  593. * Focuses last row still being edited or create row (if it exists)
  594. *
  595. * @return {AJS.RestfulTable}
  596. */
  597. _shiftFocusAfterEdit: function () {
  598. if (this.editRows.length > 0) {
  599. this.editRows[this.editRows.length-1].trigger(this._events.FOCUS);
  600. } else if (this._createRow) {
  601. this._createRow.trigger(this._events.FOCUS);
  602. }
  603. return this;
  604. },
  605. /**
  606. * Evaluate if we save row when we blur. We can only do this when there is one row being edited at a time, otherwise
  607. * it causes an infinate loop JRADEV-5325
  608. *
  609. * @return {boolean}
  610. */
  611. _saveEditRowOnBlur: function () {
  612. return this.editRows.length <= 1;
  613. },
  614. /**
  615. * Dismisses rows being edited concurrently that have no changes
  616. */
  617. dismissEditRows: function () {
  618. var instance = this;
  619. $.each(this.editRows, function () {
  620. if (!this.hasUpdates()) {
  621. this.trigger(instance._events.FINISHED_EDITING);
  622. }
  623. });
  624. },
  625. /**
  626. * Converts readonly row to editable view
  627. *
  628. * @param {Backbone.View} row
  629. * @param {String} field - field name to focus
  630. * @return {Backbone.View} editRow
  631. */
  632. edit: function (row, field) {
  633. var instance = this,
  634. editRow = new this.options.views.editRow({
  635. el: row.el,
  636. columns: this.options.columns,
  637. isUpdateMode: true,
  638. allowReorder: this.options.allowReorder,
  639. fieldFocusSelector: this.options.fieldFocusSelector,
  640. model: row.model,
  641. cancelAccessKey: this.options.cancelAccessKey,
  642. submitAccessKey: this.options.submitAccessKey
  643. }),
  644. values = row.model.toJSON();
  645. values.update = true;
  646. editRow.render({
  647. errors: {},
  648. update: true,
  649. values: values
  650. })
  651. .bind(instance._events.UPDATED, function (model, focusUpdated) {
  652. instance._removeEditRow (this);
  653. this.unbind();
  654. row.render().delegateEvents(); // render and rebind events
  655. row.trigger(instance._events.UPDATED); // trigger blur fade out
  656. if (focusUpdated !== false) {
  657. instance._shiftFocusAfterEdit();
  658. }
  659. })
  660. .bind(instance._events.VALIDATION_ERROR, function () {
  661. this.trigger(instance._events.FOCUS);
  662. })
  663. .bind(instance._events.FINISHED_EDITING, function () {
  664. instance._removeEditRow(this);
  665. row.render().delegateEvents();
  666. this.unbind(); // avoid any other updating, blurring, finished editing, cancel events being fired
  667. })
  668. .bind(instance._events.CANCEL, function () {
  669. instance._removeEditRow(this);
  670. this.unbind(); // avoid any other updating, blurring, finished editing, cancel events being fired
  671. row.render().delegateEvents(); // render and rebind events
  672. instance._shiftFocusAfterEdit();
  673. })
  674. .bind(instance._events.BLUR, function () {
  675. instance.dismissEditRows(); // dismiss edit rows that have no changes
  676. if (instance._saveEditRowOnBlur()) {
  677. this.trigger(instance._events.SAVE, false); // save row, which if successful will call the updated event above
  678. }
  679. });
  680. // Ensure that if focus is pulled to another row, we blur the edit row
  681. this._applyFocusCoordinator(editRow);
  682. // focus edit row, which has the flow on effect of blurring current focused row
  683. editRow.trigger(instance._events.FOCUS, field);
  684. // disables form fields
  685. if (instance._createRow) {
  686. instance._createRow.disable();
  687. }
  688. this.editRows.push(editRow);
  689. return editRow;
  690. },
  691. /**
  692. * Renders all specified rows
  693. *
  694. * @param {Array} array of objects describing Backbone.Model's to render
  695. * @return {AJS.RestfulTable}
  696. */
  697. renderRows: function (rows) {
  698. var comparator = this._models.comparator, els = [];
  699. this._models.comparator = undefined; // disable temporarily, assume rows are sorted
  700. var models = _.map(rows, function(row) {
  701. var model = new this.options.model(row);
  702. els.push(this._renderRow(model, -1).el);
  703. return model;
  704. }, this);
  705. this._models.add(models, {silent:true});
  706. this._models.comparator = comparator;
  707. this.removeNoEntriesMsg();
  708. this.$tbody.append(els);
  709. return this;
  710. },
  711. /**
  712. * Gets default options
  713. *
  714. * @param {Object} options
  715. */
  716. _getDefaultOptions: function (options) {
  717. return {
  718. model: options.model || AJS.RestfulTable.EntryModel,
  719. allowEdit: true,
  720. views: {
  721. editRow: AJS.RestfulTable.EditRow,
  722. row: AJS.RestfulTable.Row
  723. },
  724. Collection: Backbone.Collection.extend({
  725. url: options.resources.self,
  726. model: options.model || AJS.RestfulTable.EntryModel
  727. }),
  728. allowReorder: false,
  729. fieldFocusSelector: function(name) {
  730. return ":input[name=" + name + "], #" + name;
  731. },
  732. loadingMsg: options.loadingMsg || AJS.I18n.getText("aui.words.loading")
  733. }
  734. }
  735. });
  736. // jQuery data keys (http://api.jquery.com/jQuery.data/)
  737. AJS.RestfulTable.DataKeys = {
  738. ENABLED_SUBMIT: "enabledSubmit",
  739. ROW_VIEW: "RestfulTable_Row_View"
  740. };
  741. // CSS style classes. DON'T hard code
  742. AJS.RestfulTable.ClassNames = {
  743. NO_VALUE: "aui-restfultable-editable-no-value",
  744. NO_ENTRIES: "aui-restfultable-no-entires",
  745. RESTFUL_TABLE: "aui-restfultable",
  746. ROW: "aui-restfultable-row",
  747. READ_ONLY: "aui-restfultable-readonly",
  748. ACTIVE: "aui-restfultable-active",
  749. ALLOW_HOVER: "aui-restfultable-allowhover",
  750. FOCUSED: "aui-restfultable-focused",
  751. MOVEABLE: "aui-restfultable-movable",
  752. ANIMATING: "aui-restfultable-animate",
  753. DISABLED: "aui-restfultable-disabled",
  754. SUBMIT: "aui-restfultable-submit",
  755. CANCEL: "aui-restfultable-cancel",
  756. EDIT_ROW: "aui-restfultable-editrow",
  757. CREATE: "aui-restfultable-create",
  758. DRAG_HANDLE: "aui-restfultable-draghandle",
  759. ORDER: "aui-restfultable-order",
  760. EDITABLE: "aui-restfultable-editable",
  761. ERROR: "error",
  762. DELETE: "aui-restfultable-delete",
  763. LOADING: "loading"
  764. };
  765. // Custom events
  766. AJS.RestfulTable.Events = {
  767. // AJS events
  768. REORDER_SUCCESS: "RestfulTable.reorderSuccess",
  769. ROW_ADDED: "RestfulTable.rowAdded",
  770. ROW_REMOVED: "RestfulTable.rowRemoved",
  771. EDIT_ROW: "RestfulTable.switchedToEditMode",
  772. SERVER_ERROR: "RestfulTable.serverError",
  773. // backbone events
  774. CREATED: "created",
  775. UPDATED: "updated",
  776. FOCUS: "focus",
  777. BLUR: "blur",
  778. SUBMIT: "submit",
  779. SAVE: "save",
  780. MODAL: "modal",
  781. MODELESS: "modeless",
  782. CANCEL: "cancel",
  783. CONTENT_REFRESHED: "contentRefreshed",
  784. RENDER: "render",
  785. FINISHED_EDITING: "finishedEditing",
  786. VALIDATION_ERROR: "validationError",
  787. SUBMIT_STARTED: "submitStarted",
  788. SUBMIT_FINISHED: "submitFinished",
  789. ANIMATION_STARTED: "animationStarted",
  790. ANIMATION_FINISHED: "animationFinisehd",
  791. INITIALIZED: "initialized",
  792. ROW_INITIALIZED: "rowInitialized",
  793. ROW_EDIT: "editRow"
  794. };
  795. })(AJS.$);