PageRenderTime 39ms CodeModel.GetById 2ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 0ms

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

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