PageRenderTime 63ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

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

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