PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

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

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