diff --git a/demo/index.html b/demo/index.html index c941752a..fdbc8d2b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -28,6 +28,7 @@ + diff --git a/src/view-grid.js b/src/view-grid.js new file mode 100644 index 00000000..80d9820b --- /dev/null +++ b/src/view-grid.js @@ -0,0 +1,325 @@ +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { +// ## DataTable +// +// Provides a tabular view on a Dataset. +// +// Initialize it with a recline.Dataset object. +// +// Additional options passed in second arguments. Options: +// +// * cellRenderer: function used to render individual cells. See DataTableRow for more. +my.DataTable = Backbone.View.extend({ + tagName: "div", + className: "data-table-container", + + initialize: function(modelEtc, options) { + var self = this; + this.el = $(this.el); + _.bindAll(this, 'render'); + this.model.currentDocuments.bind('add', this.render); + this.model.currentDocuments.bind('reset', this.render); + this.model.currentDocuments.bind('remove', this.render); + this.state = {}; + this.hiddenFields = []; + this.options = options; + }, + + events: { + 'click .column-header-menu': 'onColumnHeaderClick' + , 'click .row-header-menu': 'onRowHeaderClick' + , 'click .root-header-menu': 'onRootHeaderClick' + , 'click .data-table-menu li a': 'onMenuClick' + }, + + // TODO: delete or re-enable (currently this code is not used from anywhere except deprecated or disabled methods (see above)). + // showDialog: function(template, data) { + // if (!data) data = {}; + // util.show('dialog'); + // util.render(template, 'dialog-content', data); + // util.observeExit($('.dialog-content'), function() { + // util.hide('dialog'); + // }) + // $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); + // }, + + + // ====================================================== + // Column and row menus + + onColumnHeaderClick: function(e) { + this.state.currentColumn = $(e.target).closest('.column-header').attr('data-field'); + util.position('data-table-menu', e); + util.render('columnActions', 'data-table-menu'); + }, + + onRowHeaderClick: function(e) { + this.state.currentRow = $(e.target).parents('tr:first').attr('data-id'); + util.position('data-table-menu', e); + util.render('rowActions', 'data-table-menu'); + }, + + onRootHeaderClick: function(e) { + util.position('data-table-menu', e); + util.render('rootActions', 'data-table-menu', {'columns': this.hiddenFields}); + }, + + onMenuClick: function(e) { + var self = this; + e.preventDefault(); + var actions = { + bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) }, + transform: function() { self.showTransformDialog('transform') }, + sortAsc: function() { self.setColumnSort('asc') }, + sortDesc: function() { self.setColumnSort('desc') }, + hideColumn: function() { self.hideColumn() }, + showColumn: function() { self.showColumn(e) }, + // TODO: Delete or re-implement ... + csv: function() { window.location.href = app.csvUrl }, + json: function() { window.location.href = "_rewrite/api/json" }, + urlImport: function() { showDialog('urlImport') }, + pasteImport: function() { showDialog('pasteImport') }, + uploadImport: function() { showDialog('uploadImport') }, + // END TODO + deleteColumn: function() { + var msg = "Are you sure? This will delete '" + self.state.currentColumn + "' from all documents."; + // TODO: + alert('This function needs to be re-implemented'); + return; + if (confirm(msg)) costco.deleteColumn(self.state.currentColumn); + }, + deleteRow: function() { + var doc = _.find(self.model.currentDocuments.models, function(doc) { + // important this is == as the currentRow will be string (as comes + // from DOM) while id may be int + return doc.id == self.state.currentRow + }); + doc.destroy().then(function() { + self.model.currentDocuments.remove(doc); + my.notify("Row deleted successfully"); + }) + .fail(function(err) { + my.notify("Errorz! " + err) + }) + } + } + util.hide('data-table-menu'); + actions[$(e.target).attr('data-action')](); + }, + + showTransformColumnDialog: function() { + var $el = $('.dialog-content'); + util.show('dialog'); + var view = new my.ColumnTransform({ + model: this.model + }); + view.state = this.state; + view.render(); + $el.empty(); + $el.append(view.el); + util.observeExit($el, function() { + util.hide('dialog'); + }) + $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); + }, + + showTransformDialog: function() { + var $el = $('.dialog-content'); + util.show('dialog'); + var view = new recline.View.DataTransform({ + }); + view.render(); + $el.empty(); + $el.append(view.el); + util.observeExit($el, function() { + util.hide('dialog'); + }) + $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); + }, + + setColumnSort: function(order) { + this.model.query({ + sort: [ + [this.state.currentColumn, order] + ] + }); + }, + + hideColumn: function() { + this.hiddenFields.push(this.state.currentColumn); + this.render(); + }, + + showColumn: function(e) { + this.hiddenFields = _.without(this.hiddenFields, $(e.target).data('column')); + this.render(); + }, + + // ====================================================== + // #### Templating + template: ' \ + \ + \ + \ + \ + \ + {{#notEmpty}} \ + \ + {{/notEmpty}} \ + {{#fields}} \ + \ + {{/fields}} \ + \ + \ + \ +
\ +
\ + \ + \ +
\ +
\ +
\ + \ + {{label}} \ +
\ + \ +
\ + ', + + toTemplateJSON: function() { + var modelData = this.model.toJSON() + modelData.notEmpty = ( this.fields.length > 0 ) + // TODO: move this sort of thing into a toTemplateJSON method on Dataset? + modelData.fields = _.map(this.fields, function(field) { return field.toJSON() }); + return modelData; + }, + render: function() { + var self = this; + this.fields = this.model.fields.filter(function(field) { + return _.indexOf(self.hiddenFields, field.id) == -1; + }); + var htmls = $.mustache(this.template, this.toTemplateJSON()); + this.el.html(htmls); + this.model.currentDocuments.forEach(function(doc) { + var tr = $(''); + self.el.find('tbody').append(tr); + var newView = new my.DataTableRow({ + model: doc, + el: tr, + fields: self.fields, + }, + self.options + ); + newView.render(); + }); + this.el.toggleClass('no-hidden', (self.hiddenFields.length == 0)); + return this; + } +}); + +// ## DataTableRow View for rendering an individual document. +// +// Since we want this to update in place it is up to creator to provider the element to attach to. +// In addition you must pass in a fields in the constructor options. This should be list of fields for the DataTable. +// +// Additional options can be passed in a second hash argument. Options: +// +// * cellRenderer: function to render cells. Signature: function(value, +// field, doc) where value is the value of this cell, field is +// corresponding field object and document is the document object. Note +// that implementing functions can ignore arguments (e.g. +// function(value) would be a valid cellRenderer function). +my.DataTableRow = Backbone.View.extend({ + initialize: function(initData, options) { + _.bindAll(this, 'render'); + this._fields = initData.fields; + if (options && options.cellRenderer) { + this._cellRenderer = options.cellRenderer; + } else { + this._cellRenderer = function(value) { + return value; + } + } + this.el = $(this.el); + this.model.bind('change', this.render); + }, + + template: ' \ + \ + {{#cells}} \ + \ +
\ +   \ +
{{{value}}}
\ +
\ + \ + {{/cells}} \ + ', + events: { + 'click .data-table-cell-edit': 'onEditClick', + 'click .data-table-cell-editor .okButton': 'onEditorOK', + 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' + }, + + toTemplateJSON: function() { + var self = this; + var doc = this.model; + var cellData = this._fields.map(function(field) { + return { + field: field.id, + value: self._cellRenderer(doc.get(field.id), field, doc) + } + }) + return { id: this.id, cells: cellData } + }, + + render: function() { + this.el.attr('data-id', this.model.id); + var html = $.mustache(this.template, this.toTemplateJSON()); + $(this.el).html(html); + return this; + }, + + // Cell Editor + // =========== + + onEditClick: function(e) { + var editing = this.el.find('.data-table-cell-editor-editor'); + if (editing.length > 0) { + editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); + } + $(e.target).addClass("hidden"); + var cell = $(e.target).siblings('.data-table-cell-value'); + cell.data("previousContents", cell.text()); + util.render('cellEditor', cell, {value: cell.text()}); + }, + + onEditorOK: function(e) { + var cell = $(e.target); + var rowId = cell.parents('tr').attr('data-id'); + var field = cell.parents('td').attr('data-field'); + var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); + var newData = {}; + newData[field] = newValue; + this.model.set(newData); + my.notify("Updating row...", {loader: true}); + this.model.save().then(function(response) { + my.notify("Row updated successfully", {category: 'success'}); + }) + .fail(function() { + my.notify('Error saving row', { + category: 'error', + persist: true + }); + }); + }, + + onEditorCancel: function(e) { + var cell = $(e.target).parents('.data-table-cell-value'); + cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); + } +}); + +})(jQuery, recline.View); diff --git a/src/view.js b/src/view.js index 31418cdc..bb35adc9 100644 --- a/src/view.js +++ b/src/view.js @@ -190,326 +190,6 @@ my.DataExplorer = Backbone.View.extend({ } }); -// ## DataTable -// -// Provides a tabular view on a Dataset. -// -// Initialize it with a recline.Dataset object. -// -// Additional options passed in second arguments. Options: -// -// * cellRenderer: function used to render individual cells. See DataTableRow for more. -my.DataTable = Backbone.View.extend({ - tagName: "div", - className: "data-table-container", - - initialize: function(modelEtc, options) { - var self = this; - this.el = $(this.el); - _.bindAll(this, 'render'); - this.model.currentDocuments.bind('add', this.render); - this.model.currentDocuments.bind('reset', this.render); - this.model.currentDocuments.bind('remove', this.render); - this.state = {}; - this.hiddenFields = []; - this.options = options; - }, - - events: { - 'click .column-header-menu': 'onColumnHeaderClick' - , 'click .row-header-menu': 'onRowHeaderClick' - , 'click .root-header-menu': 'onRootHeaderClick' - , 'click .data-table-menu li a': 'onMenuClick' - }, - - // TODO: delete or re-enable (currently this code is not used from anywhere except deprecated or disabled methods (see above)). - // showDialog: function(template, data) { - // if (!data) data = {}; - // util.show('dialog'); - // util.render(template, 'dialog-content', data); - // util.observeExit($('.dialog-content'), function() { - // util.hide('dialog'); - // }) - // $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); - // }, - - - // ====================================================== - // Column and row menus - - onColumnHeaderClick: function(e) { - this.state.currentColumn = $(e.target).closest('.column-header').attr('data-field'); - util.position('data-table-menu', e); - util.render('columnActions', 'data-table-menu'); - }, - - onRowHeaderClick: function(e) { - this.state.currentRow = $(e.target).parents('tr:first').attr('data-id'); - util.position('data-table-menu', e); - util.render('rowActions', 'data-table-menu'); - }, - - onRootHeaderClick: function(e) { - util.position('data-table-menu', e); - util.render('rootActions', 'data-table-menu', {'columns': this.hiddenFields}); - }, - - onMenuClick: function(e) { - var self = this; - e.preventDefault(); - var actions = { - bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) }, - transform: function() { self.showTransformDialog('transform') }, - sortAsc: function() { self.setColumnSort('asc') }, - sortDesc: function() { self.setColumnSort('desc') }, - hideColumn: function() { self.hideColumn() }, - showColumn: function() { self.showColumn(e) }, - // TODO: Delete or re-implement ... - csv: function() { window.location.href = app.csvUrl }, - json: function() { window.location.href = "_rewrite/api/json" }, - urlImport: function() { showDialog('urlImport') }, - pasteImport: function() { showDialog('pasteImport') }, - uploadImport: function() { showDialog('uploadImport') }, - // END TODO - deleteColumn: function() { - var msg = "Are you sure? This will delete '" + self.state.currentColumn + "' from all documents."; - // TODO: - alert('This function needs to be re-implemented'); - return; - if (confirm(msg)) costco.deleteColumn(self.state.currentColumn); - }, - deleteRow: function() { - var doc = _.find(self.model.currentDocuments.models, function(doc) { - // important this is == as the currentRow will be string (as comes - // from DOM) while id may be int - return doc.id == self.state.currentRow - }); - doc.destroy().then(function() { - self.model.currentDocuments.remove(doc); - my.notify("Row deleted successfully"); - }) - .fail(function(err) { - my.notify("Errorz! " + err) - }) - } - } - util.hide('data-table-menu'); - actions[$(e.target).attr('data-action')](); - }, - - showTransformColumnDialog: function() { - var $el = $('.dialog-content'); - util.show('dialog'); - var view = new my.ColumnTransform({ - model: this.model - }); - view.state = this.state; - view.render(); - $el.empty(); - $el.append(view.el); - util.observeExit($el, function() { - util.hide('dialog'); - }) - $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); - }, - - showTransformDialog: function() { - var $el = $('.dialog-content'); - util.show('dialog'); - var view = new recline.View.DataTransform({ - }); - view.render(); - $el.empty(); - $el.append(view.el); - util.observeExit($el, function() { - util.hide('dialog'); - }) - $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); - }, - - setColumnSort: function(order) { - this.model.query({ - sort: [ - [this.state.currentColumn, order] - ] - }); - }, - - hideColumn: function() { - this.hiddenFields.push(this.state.currentColumn); - this.render(); - }, - - showColumn: function(e) { - this.hiddenFields = _.without(this.hiddenFields, $(e.target).data('column')); - this.render(); - }, - - // ====================================================== - // #### Templating - template: ' \ - \ - \ - \ - \ - \ - {{#notEmpty}} \ - \ - {{/notEmpty}} \ - {{#fields}} \ - \ - {{/fields}} \ - \ - \ - \ -
\ -
\ - \ - \ -
\ -
\ -
\ - \ - {{label}} \ -
\ - \ -
\ - ', - - toTemplateJSON: function() { - var modelData = this.model.toJSON() - modelData.notEmpty = ( this.fields.length > 0 ) - // TODO: move this sort of thing into a toTemplateJSON method on Dataset? - modelData.fields = _.map(this.fields, function(field) { return field.toJSON() }); - return modelData; - }, - render: function() { - var self = this; - this.fields = this.model.fields.filter(function(field) { - return _.indexOf(self.hiddenFields, field.id) == -1; - }); - var htmls = $.mustache(this.template, this.toTemplateJSON()); - this.el.html(htmls); - this.model.currentDocuments.forEach(function(doc) { - var tr = $(''); - self.el.find('tbody').append(tr); - var newView = new my.DataTableRow({ - model: doc, - el: tr, - fields: self.fields, - }, - self.options - ); - newView.render(); - }); - this.el.toggleClass('no-hidden', (self.hiddenFields.length == 0)); - return this; - } -}); - -// ## DataTableRow View for rendering an individual document. -// -// Since we want this to update in place it is up to creator to provider the element to attach to. -// In addition you must pass in a fields in the constructor options. This should be list of fields for the DataTable. -// -// Additional options can be passed in a second hash argument. Options: -// -// * cellRenderer: function to render cells. Signature: function(value, -// field, doc) where value is the value of this cell, field is -// corresponding field object and document is the document object. Note -// that implementing functions can ignore arguments (e.g. -// function(value) would be a valid cellRenderer function). -my.DataTableRow = Backbone.View.extend({ - initialize: function(initData, options) { - _.bindAll(this, 'render'); - this._fields = initData.fields; - if (options && options.cellRenderer) { - this._cellRenderer = options.cellRenderer; - } else { - this._cellRenderer = function(value) { - return value; - } - } - this.el = $(this.el); - this.model.bind('change', this.render); - }, - - template: ' \ - \ - {{#cells}} \ - \ -
\ -   \ -
{{{value}}}
\ -
\ - \ - {{/cells}} \ - ', - events: { - 'click .data-table-cell-edit': 'onEditClick', - 'click .data-table-cell-editor .okButton': 'onEditorOK', - 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' - }, - - toTemplateJSON: function() { - var self = this; - var doc = this.model; - var cellData = this._fields.map(function(field) { - return { - field: field.id, - value: self._cellRenderer(doc.get(field.id), field, doc) - } - }) - return { id: this.id, cells: cellData } - }, - - render: function() { - this.el.attr('data-id', this.model.id); - var html = $.mustache(this.template, this.toTemplateJSON()); - $(this.el).html(html); - return this; - }, - - // Cell Editor - // =========== - - onEditClick: function(e) { - var editing = this.el.find('.data-table-cell-editor-editor'); - if (editing.length > 0) { - editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); - } - $(e.target).addClass("hidden"); - var cell = $(e.target).siblings('.data-table-cell-value'); - cell.data("previousContents", cell.text()); - util.render('cellEditor', cell, {value: cell.text()}); - }, - - onEditorOK: function(e) { - var cell = $(e.target); - var rowId = cell.parents('tr').attr('data-id'); - var field = cell.parents('td').attr('data-field'); - var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); - var newData = {}; - newData[field] = newValue; - this.model.set(newData); - my.notify("Updating row...", {loader: true}); - this.model.save().then(function(response) { - my.notify("Row updated successfully", {category: 'success'}); - }) - .fail(function() { - my.notify('Error saving row', { - category: 'error', - persist: true - }); - }); - }, - - onEditorCancel: function(e) { - var cell = $(e.target).parents('.data-table-cell-value'); - cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); - } -}); - /* ========================================================== */ // ## Miscellaneous Utilities diff --git a/test/index.html b/test/index.html index cd87c9af..a00c9f81 100644 --- a/test/index.html +++ b/test/index.html @@ -21,6 +21,7 @@ +