[#41,view,refactor][s]: merge DataTable and DataExplorer views back into view.js.
* Also improve docs for views a little bit (better use or markdown)
This commit is contained in:
parent
98580d63d4
commit
23e46e9c6f
@ -28,8 +28,6 @@
|
||||
<script type="text/javascript" src="../src/model.js"></script>
|
||||
<script type="text/javascript" src="../src/backend.js"></script>
|
||||
<script type="text/javascript" src="../src/view.js"></script>
|
||||
<script type="text/javascript" src="../src/view-data-explorer.js"></script>
|
||||
<script type="text/javascript" src="../src/view-data-table.js"></script>
|
||||
<script type="text/javascript" src="../src/view-flot-graph.js"></script>
|
||||
<script type="text/javascript" src="../src/view-transform-dialog.js"></script>
|
||||
<script type="text/javascript" src="js/app.js"></script>
|
||||
|
||||
@ -1,171 +0,0 @@
|
||||
this.recline = this.recline || {};
|
||||
this.recline.View = this.recline.View || {};
|
||||
|
||||
// Views module following classic module pattern
|
||||
(function($, my) {
|
||||
|
||||
// The primary view for the entire application.
|
||||
//
|
||||
// It should be initialized with a recline.Model.Dataset object and an existing
|
||||
// dom element to attach to (the existing DOM element is important for
|
||||
// rendering of FlotGraph subview).
|
||||
//
|
||||
// To pass in configuration options use the config key in initialization hash
|
||||
// e.g.
|
||||
//
|
||||
// var explorer = new DataExplorer({
|
||||
// config: {...}
|
||||
// })
|
||||
//
|
||||
// Config options:
|
||||
//
|
||||
// * displayCount: how many documents to display initially (default: 10)
|
||||
// * readOnly: true/false (default: false) value indicating whether to
|
||||
// operate in read-only mode (hiding all editing options).
|
||||
//
|
||||
// All other views as contained in this one.
|
||||
my.DataExplorer = Backbone.View.extend({
|
||||
template: ' \
|
||||
<div class="data-explorer"> \
|
||||
<div class="alert-messages"></div> \
|
||||
\
|
||||
<div class="header"> \
|
||||
<ul class="navigation"> \
|
||||
<li class="active"><a href="#grid" class="btn">Grid</a> \
|
||||
<li><a href="#graph" class="btn">Graph</a></li> \
|
||||
</ul> \
|
||||
<div class="pagination"> \
|
||||
<form class="display-count"> \
|
||||
Showing 0 to <input name="displayCount" type="text" value="{{displayCount}}" title="Edit and hit enter to change the number of rows displayed" /> of <span class="doc-count">{{docCount}}</span> \
|
||||
</form> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="data-view-container"></div> \
|
||||
<div class="dialog-overlay" style="display: none; z-index: 101; "> </div> \
|
||||
<div class="dialog ui-draggable" style="display: none; z-index: 102; top: 101px; "> \
|
||||
<div class="dialog-frame" style="width: 700px; visibility: visible; "> \
|
||||
<div class="dialog-content dialog-border"></div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
',
|
||||
|
||||
events: {
|
||||
'submit form.display-count': 'onDisplayCountUpdate'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.el = $(this.el);
|
||||
this.config = _.extend({
|
||||
displayCount: 50
|
||||
, readOnly: false
|
||||
},
|
||||
options.config);
|
||||
if (this.config.readOnly) {
|
||||
this.setReadOnly();
|
||||
}
|
||||
// Hash of 'page' views (i.e. those for whole page) keyed by page name
|
||||
this.pageViews = {
|
||||
grid: new my.DataTable({
|
||||
model: this.model,
|
||||
config: this.config
|
||||
})
|
||||
, graph: new my.FlotGraph({
|
||||
model: this.model
|
||||
})
|
||||
};
|
||||
// this must be called after pageViews are created
|
||||
this.render();
|
||||
|
||||
this.router = new Backbone.Router();
|
||||
this.setupRouting();
|
||||
|
||||
// retrieve basic data like headers etc
|
||||
// note this.model and dataset returned are the same
|
||||
this.model.fetch()
|
||||
.done(function(dataset) {
|
||||
self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
|
||||
self.query();
|
||||
})
|
||||
.fail(function(error) {
|
||||
my.notify(error.message, {category: 'error', persist: true});
|
||||
});
|
||||
},
|
||||
|
||||
query: function() {
|
||||
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
|
||||
var queryObj = {
|
||||
size: this.config.displayCount
|
||||
};
|
||||
my.notify('Loading data', {loader: true});
|
||||
this.model.query(queryObj)
|
||||
.done(function() {
|
||||
my.clearNotifications();
|
||||
my.notify('Data loaded', {category: 'success'});
|
||||
})
|
||||
.fail(function(error) {
|
||||
my.clearNotifications();
|
||||
my.notify(error.message, {category: 'error', persist: true});
|
||||
});
|
||||
},
|
||||
|
||||
onDisplayCountUpdate: function(e) {
|
||||
e.preventDefault();
|
||||
this.query();
|
||||
},
|
||||
|
||||
setReadOnly: function() {
|
||||
this.el.addClass('read-only');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tmplData = this.model.toTemplateJSON();
|
||||
tmplData.displayCount = this.config.displayCount;
|
||||
var template = $.mustache(this.template, tmplData);
|
||||
$(this.el).html(template);
|
||||
var $dataViewContainer = this.el.find('.data-view-container');
|
||||
_.each(this.pageViews, function(view, pageName) {
|
||||
$dataViewContainer.append(view.el)
|
||||
});
|
||||
},
|
||||
|
||||
setupRouting: function() {
|
||||
var self = this;
|
||||
this.router.route('', 'grid', function() {
|
||||
self.updateNav('grid');
|
||||
});
|
||||
this.router.route(/grid(\?.*)?/, 'view', function(queryString) {
|
||||
self.updateNav('grid', queryString);
|
||||
});
|
||||
this.router.route(/graph(\?.*)?/, 'graph', function(queryString) {
|
||||
self.updateNav('graph', queryString);
|
||||
// we have to call here due to fact plot may not have been able to draw
|
||||
// if it was hidden until now - see comments in FlotGraph.redraw
|
||||
qsParsed = parseQueryString(queryString);
|
||||
if ('graph' in qsParsed) {
|
||||
var chartConfig = JSON.parse(qsParsed['graph']);
|
||||
_.extend(self.pageViews['graph'].chartConfig, chartConfig);
|
||||
}
|
||||
self.pageViews['graph'].redraw();
|
||||
});
|
||||
},
|
||||
|
||||
updateNav: function(pageName, queryString) {
|
||||
this.el.find('.navigation li').removeClass('active');
|
||||
var $el = this.el.find('.navigation li a[href=#' + pageName + ']');
|
||||
$el.parent().addClass('active');
|
||||
// show the specific page
|
||||
_.each(this.pageViews, function(view, pageViewName) {
|
||||
if (pageViewName === pageName) {
|
||||
view.el.show();
|
||||
} else {
|
||||
view.el.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, recline.View);
|
||||
|
||||
|
||||
@ -1,298 +0,0 @@
|
||||
this.recline = this.recline || {};
|
||||
this.recline.View = this.recline.View || {};
|
||||
|
||||
// Views module following classic module pattern
|
||||
(function($, my) {
|
||||
|
||||
// DataTable provides a tabular view on a Dataset.
|
||||
//
|
||||
// Initialize it with a recline.Dataset object.
|
||||
my.DataTable = Backbone.View.extend({
|
||||
tagName: "div",
|
||||
className: "data-table-container",
|
||||
|
||||
initialize: function() {
|
||||
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.hiddenHeaders = [];
|
||||
},
|
||||
|
||||
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).siblings().text();
|
||||
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.hiddenHeaders});
|
||||
},
|
||||
|
||||
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) {
|
||||
var query = _.extend(this.model.queryState, {sort: [[this.state.currentColumn, order]]});
|
||||
this.model.query(query);
|
||||
},
|
||||
|
||||
hideColumn: function() {
|
||||
this.hiddenHeaders.push(this.state.currentColumn);
|
||||
this.render();
|
||||
},
|
||||
|
||||
showColumn: function(e) {
|
||||
this.hiddenHeaders = _.without(this.hiddenHeaders, $(e.target).data('column'));
|
||||
this.render();
|
||||
},
|
||||
|
||||
// ======================================================
|
||||
// Core Templating
|
||||
template: ' \
|
||||
<div class="data-table-menu-overlay" style="display: none; z-index: 101; "> </div> \
|
||||
<ul class="data-table-menu"></ul> \
|
||||
<table class="data-table" cellspacing="0"> \
|
||||
<thead> \
|
||||
<tr> \
|
||||
{{#notEmpty}} \
|
||||
<th class="column-header"> \
|
||||
<div class="column-header-title"> \
|
||||
<a class="root-header-menu"></a> \
|
||||
<span class="column-header-name"></span> \
|
||||
</div> \
|
||||
</th> \
|
||||
{{/notEmpty}} \
|
||||
{{#headers}} \
|
||||
<th class="column-header"> \
|
||||
<div class="column-header-title"> \
|
||||
<a class="column-header-menu"></a> \
|
||||
<span class="column-header-name">{{.}}</span> \
|
||||
</div> \
|
||||
</div> \
|
||||
</th> \
|
||||
{{/headers}} \
|
||||
</tr> \
|
||||
</thead> \
|
||||
<tbody></tbody> \
|
||||
</table> \
|
||||
',
|
||||
|
||||
toTemplateJSON: function() {
|
||||
var modelData = this.model.toJSON()
|
||||
modelData.notEmpty = ( this.headers.length > 0 )
|
||||
modelData.headers = this.headers;
|
||||
return modelData;
|
||||
},
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.headers = _.filter(this.model.get('headers'), function(header) {
|
||||
return _.indexOf(self.hiddenHeaders, header) == -1;
|
||||
});
|
||||
var htmls = $.mustache(this.template, this.toTemplateJSON());
|
||||
this.el.html(htmls);
|
||||
this.model.currentDocuments.forEach(function(doc) {
|
||||
var tr = $('<tr />');
|
||||
self.el.find('tbody').append(tr);
|
||||
var newView = new my.DataTableRow({
|
||||
model: doc,
|
||||
el: tr,
|
||||
headers: self.headers,
|
||||
});
|
||||
newView.render();
|
||||
});
|
||||
this.el.toggleClass('no-hidden', (self.hiddenHeaders.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 headers in the constructor options. This should be list of headers for the DataTable.
|
||||
my.DataTableRow = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
_.bindAll(this, 'render');
|
||||
this._headers = options.headers;
|
||||
this.el = $(this.el);
|
||||
this.model.bind('change', this.render);
|
||||
},
|
||||
|
||||
template: ' \
|
||||
<td><a class="row-header-menu"></a></td> \
|
||||
{{#cells}} \
|
||||
<td data-header="{{header}}"> \
|
||||
<div class="data-table-cell-content"> \
|
||||
<a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell"> </a> \
|
||||
<div class="data-table-cell-value">{{value}}</div> \
|
||||
</div> \
|
||||
</td> \
|
||||
{{/cells}} \
|
||||
',
|
||||
events: {
|
||||
'click .data-table-cell-edit': 'onEditClick',
|
||||
// cell editor
|
||||
'click .data-table-cell-editor .okButton': 'onEditorOK',
|
||||
'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
|
||||
},
|
||||
|
||||
toTemplateJSON: function() {
|
||||
var doc = this.model;
|
||||
var cellData = _.map(this._headers, function(header) {
|
||||
return {header: header, value: doc.get(header)}
|
||||
})
|
||||
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 header = cell.parents('td').attr('data-header');
|
||||
var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
|
||||
var newData = {};
|
||||
newData[header] = 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);
|
||||
|
||||
|
||||
458
src/view.js
458
src/view.js
@ -3,6 +3,464 @@ this.recline.View = this.recline.View || {};
|
||||
|
||||
// Views module following classic module pattern
|
||||
(function($, my) {
|
||||
// ## DataExplorer
|
||||
//
|
||||
// The primary view for the entire application.
|
||||
//
|
||||
// It should be initialized with a recline.Model.Dataset object and an existing
|
||||
// dom element to attach to (the existing DOM element is important for
|
||||
// rendering of FlotGraph subview).
|
||||
//
|
||||
// To pass in configuration options use the config key in initialization hash
|
||||
// e.g.
|
||||
//
|
||||
// var explorer = new DataExplorer({
|
||||
// config: {...}
|
||||
// })
|
||||
//
|
||||
// Config options:
|
||||
//
|
||||
// * displayCount: how many documents to display initially (default: 10)
|
||||
// * readOnly: true/false (default: false) value indicating whether to
|
||||
// operate in read-only mode (hiding all editing options).
|
||||
//
|
||||
// All other views as contained in this one.
|
||||
my.DataExplorer = Backbone.View.extend({
|
||||
template: ' \
|
||||
<div class="data-explorer"> \
|
||||
<div class="alert-messages"></div> \
|
||||
\
|
||||
<div class="header"> \
|
||||
<ul class="navigation"> \
|
||||
<li class="active"><a href="#grid" class="btn">Grid</a> \
|
||||
<li><a href="#graph" class="btn">Graph</a></li> \
|
||||
</ul> \
|
||||
<div class="pagination"> \
|
||||
<form class="display-count"> \
|
||||
Showing 0 to <input name="displayCount" type="text" value="{{displayCount}}" title="Edit and hit enter to change the number of rows displayed" /> of <span class="doc-count">{{docCount}}</span> \
|
||||
</form> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="data-view-container"></div> \
|
||||
<div class="dialog-overlay" style="display: none; z-index: 101; "> </div> \
|
||||
<div class="dialog ui-draggable" style="display: none; z-index: 102; top: 101px; "> \
|
||||
<div class="dialog-frame" style="width: 700px; visibility: visible; "> \
|
||||
<div class="dialog-content dialog-border"></div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
',
|
||||
|
||||
events: {
|
||||
'submit form.display-count': 'onDisplayCountUpdate'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.el = $(this.el);
|
||||
this.config = _.extend({
|
||||
displayCount: 50
|
||||
, readOnly: false
|
||||
},
|
||||
options.config);
|
||||
if (this.config.readOnly) {
|
||||
this.setReadOnly();
|
||||
}
|
||||
// Hash of 'page' views (i.e. those for whole page) keyed by page name
|
||||
this.pageViews = {
|
||||
grid: new my.DataTable({
|
||||
model: this.model,
|
||||
config: this.config
|
||||
})
|
||||
, graph: new my.FlotGraph({
|
||||
model: this.model
|
||||
})
|
||||
};
|
||||
// this must be called after pageViews are created
|
||||
this.render();
|
||||
|
||||
this.router = new Backbone.Router();
|
||||
this.setupRouting();
|
||||
|
||||
// retrieve basic data like headers etc
|
||||
// note this.model and dataset returned are the same
|
||||
this.model.fetch()
|
||||
.done(function(dataset) {
|
||||
self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
|
||||
self.query();
|
||||
})
|
||||
.fail(function(error) {
|
||||
my.notify(error.message, {category: 'error', persist: true});
|
||||
});
|
||||
},
|
||||
|
||||
query: function() {
|
||||
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
|
||||
var queryObj = {
|
||||
size: this.config.displayCount
|
||||
};
|
||||
my.notify('Loading data', {loader: true});
|
||||
this.model.query(queryObj)
|
||||
.done(function() {
|
||||
my.clearNotifications();
|
||||
my.notify('Data loaded', {category: 'success'});
|
||||
})
|
||||
.fail(function(error) {
|
||||
my.clearNotifications();
|
||||
my.notify(error.message, {category: 'error', persist: true});
|
||||
});
|
||||
},
|
||||
|
||||
onDisplayCountUpdate: function(e) {
|
||||
e.preventDefault();
|
||||
this.query();
|
||||
},
|
||||
|
||||
setReadOnly: function() {
|
||||
this.el.addClass('read-only');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tmplData = this.model.toTemplateJSON();
|
||||
tmplData.displayCount = this.config.displayCount;
|
||||
var template = $.mustache(this.template, tmplData);
|
||||
$(this.el).html(template);
|
||||
var $dataViewContainer = this.el.find('.data-view-container');
|
||||
_.each(this.pageViews, function(view, pageName) {
|
||||
$dataViewContainer.append(view.el)
|
||||
});
|
||||
},
|
||||
|
||||
setupRouting: function() {
|
||||
var self = this;
|
||||
this.router.route('', 'grid', function() {
|
||||
self.updateNav('grid');
|
||||
});
|
||||
this.router.route(/grid(\?.*)?/, 'view', function(queryString) {
|
||||
self.updateNav('grid', queryString);
|
||||
});
|
||||
this.router.route(/graph(\?.*)?/, 'graph', function(queryString) {
|
||||
self.updateNav('graph', queryString);
|
||||
// we have to call here due to fact plot may not have been able to draw
|
||||
// if it was hidden until now - see comments in FlotGraph.redraw
|
||||
qsParsed = parseQueryString(queryString);
|
||||
if ('graph' in qsParsed) {
|
||||
var chartConfig = JSON.parse(qsParsed['graph']);
|
||||
_.extend(self.pageViews['graph'].chartConfig, chartConfig);
|
||||
}
|
||||
self.pageViews['graph'].redraw();
|
||||
});
|
||||
},
|
||||
|
||||
updateNav: function(pageName, queryString) {
|
||||
this.el.find('.navigation li').removeClass('active');
|
||||
var $el = this.el.find('.navigation li a[href=#' + pageName + ']');
|
||||
$el.parent().addClass('active');
|
||||
// show the specific page
|
||||
_.each(this.pageViews, function(view, pageViewName) {
|
||||
if (pageViewName === pageName) {
|
||||
view.el.show();
|
||||
} else {
|
||||
view.el.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ## DataTable
|
||||
//
|
||||
// Provides a tabular view on a Dataset.
|
||||
//
|
||||
// Initialize it with a recline.Dataset object.
|
||||
my.DataTable = Backbone.View.extend({
|
||||
tagName: "div",
|
||||
className: "data-table-container",
|
||||
|
||||
initialize: function() {
|
||||
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.hiddenHeaders = [];
|
||||
},
|
||||
|
||||
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).siblings().text();
|
||||
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.hiddenHeaders});
|
||||
},
|
||||
|
||||
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) {
|
||||
var query = _.extend(this.model.queryState, {sort: [[this.state.currentColumn, order]]});
|
||||
this.model.query(query);
|
||||
},
|
||||
|
||||
hideColumn: function() {
|
||||
this.hiddenHeaders.push(this.state.currentColumn);
|
||||
this.render();
|
||||
},
|
||||
|
||||
showColumn: function(e) {
|
||||
this.hiddenHeaders = _.without(this.hiddenHeaders, $(e.target).data('column'));
|
||||
this.render();
|
||||
},
|
||||
|
||||
// ======================================================
|
||||
// #### Templating
|
||||
template: ' \
|
||||
<div class="data-table-menu-overlay" style="display: none; z-index: 101; "> </div> \
|
||||
<ul class="data-table-menu"></ul> \
|
||||
<table class="data-table" cellspacing="0"> \
|
||||
<thead> \
|
||||
<tr> \
|
||||
{{#notEmpty}} \
|
||||
<th class="column-header"> \
|
||||
<div class="column-header-title"> \
|
||||
<a class="root-header-menu"></a> \
|
||||
<span class="column-header-name"></span> \
|
||||
</div> \
|
||||
</th> \
|
||||
{{/notEmpty}} \
|
||||
{{#headers}} \
|
||||
<th class="column-header"> \
|
||||
<div class="column-header-title"> \
|
||||
<a class="column-header-menu"></a> \
|
||||
<span class="column-header-name">{{.}}</span> \
|
||||
</div> \
|
||||
</div> \
|
||||
</th> \
|
||||
{{/headers}} \
|
||||
</tr> \
|
||||
</thead> \
|
||||
<tbody></tbody> \
|
||||
</table> \
|
||||
',
|
||||
|
||||
toTemplateJSON: function() {
|
||||
var modelData = this.model.toJSON()
|
||||
modelData.notEmpty = ( this.headers.length > 0 )
|
||||
modelData.headers = this.headers;
|
||||
return modelData;
|
||||
},
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.headers = _.filter(this.model.get('headers'), function(header) {
|
||||
return _.indexOf(self.hiddenHeaders, header) == -1;
|
||||
});
|
||||
var htmls = $.mustache(this.template, this.toTemplateJSON());
|
||||
this.el.html(htmls);
|
||||
this.model.currentDocuments.forEach(function(doc) {
|
||||
var tr = $('<tr />');
|
||||
self.el.find('tbody').append(tr);
|
||||
var newView = new my.DataTableRow({
|
||||
model: doc,
|
||||
el: tr,
|
||||
headers: self.headers,
|
||||
});
|
||||
newView.render();
|
||||
});
|
||||
this.el.toggleClass('no-hidden', (self.hiddenHeaders.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 headers in the constructor options. This should be list of headers for the DataTable.
|
||||
my.DataTableRow = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
_.bindAll(this, 'render');
|
||||
this._headers = options.headers;
|
||||
this.el = $(this.el);
|
||||
this.model.bind('change', this.render);
|
||||
},
|
||||
|
||||
template: ' \
|
||||
<td><a class="row-header-menu"></a></td> \
|
||||
{{#cells}} \
|
||||
<td data-header="{{header}}"> \
|
||||
<div class="data-table-cell-content"> \
|
||||
<a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell"> </a> \
|
||||
<div class="data-table-cell-value">{{value}}</div> \
|
||||
</div> \
|
||||
</td> \
|
||||
{{/cells}} \
|
||||
',
|
||||
events: {
|
||||
'click .data-table-cell-edit': 'onEditClick',
|
||||
// cell editor
|
||||
'click .data-table-cell-editor .okButton': 'onEditorOK',
|
||||
'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
|
||||
},
|
||||
|
||||
toTemplateJSON: function() {
|
||||
var doc = this.model;
|
||||
var cellData = _.map(this._headers, function(header) {
|
||||
return {header: header, value: doc.get(header)}
|
||||
})
|
||||
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 header = cell.parents('td').attr('data-header');
|
||||
var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
|
||||
var newData = {};
|
||||
newData[header] = 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
|
||||
|
||||
// Parse a URL query string (?xyz=abc...) into a dictionary.
|
||||
function parseQueryString(q) {
|
||||
|
||||
@ -20,8 +20,6 @@
|
||||
<script type="text/javascript" src="../src/backend.js"></script>
|
||||
<script type="text/javascript" src="model.test.js"></script>
|
||||
<script type="text/javascript" src="../src/view.js"></script>
|
||||
<script type="text/javascript" src="../src/view-data-explorer.js"></script>
|
||||
<script type="text/javascript" src="../src/view-data-table.js"></script>
|
||||
<script type="text/javascript" src="../src/view-transform-dialog.js"></script>
|
||||
<script type="text/javascript" src="view.test.js"></script>
|
||||
</head>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user