this.recline = this.recline || {}; // Views module following classic module pattern recline.View = function($) { var my = {}; my.DataExplorer = Backbone.View.extend({ tagName: 'div', className: 'data-explorer', template: ' \
\ \ ', events: { 'change input[name="nav-toggle"]': 'navChange', 'submit form.display-count': 'displayCountUpdate' }, initialize: function(options) { this.el = $(this.el); this.config = options.config || {}; _.extend(this.config, { displayCount: 10 }); this.draw(); }, displayCountUpdate: function(e) { e.preventDefault(); this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val()); this.draw(); }, draw: function() { var self = this; this.el.empty(); this.render(); this.$dataViewContainer = this.el.find('.data-view-container'); // retrieve basic data like headers etc // note this.model and dataset returned are the same this.model.fetch().then(function(dataset) { // initialize of dataTable calls render self.dataTable = new my.DataTable({ model: dataset }); self.flotGraph = new my.FlotGraph({ model: dataset }); self.flotGraph.el.hide(); self.$dataViewContainer.append(self.dataTable.el) self.$dataViewContainer.append(self.flotGraph.el); self.model.getRows(self.config.displayCount); }); }, render: function() { var template = $.mustache(this.template, this.config); $(this.el).html(template); }, navChange: function(e) { // TODO: really ugly and will not scale to more widgets ... var widgetToShow = $(e.target).val(); if (widgetToShow == 'datatable') { this.flotGraph.el.hide(); this.dataTable.el.show(); } else if (widgetToShow == 'graph') { this.flotGraph.el.show(); this.dataTable.el.hide(); // Have to call this here // If you attempt to render with flot when graph is hidden / invisible flot will complain with // Invalid dimensions for plot, width = 0, height = 0 // (Could hack this by moving plot left -1000 or similar ...) this.flotGraph.createPlot(); } } }); // 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 is nasty. Due to fact that .menu element is not inside this view but is elsewhere in DOM $('.menu li a').live('click', function(e) { // self.onMenuClick(e).apply(self); self.onMenuClick(e); }); }, events: { // see initialize // 'click .menu li': 'onMenuClick', 'click .column-header-menu': 'onColumnHeaderClick', 'click .row-header-menu': 'onRowHeaderClick' }, 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('menu', e); util.render('columnActions', 'menu'); }, onRowHeaderClick: function(e) { this.state.currentRow = $(e.target).parents('tr:first').attr('data-id'); util.position('menu', e); util.render('rowActions', 'menu'); }, onMenuClick: function(e) { var self = this; e.preventDefault(); var actions = { bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) }, transform: function() { self.showTransformDialog('transform') }, 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') }, 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); util.notify("Row deleted successfully"); }) .fail(function(err) { util.notify("Errorz! " + err) }) } } util.hide('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 my.DataTransform({ }); view.render(); $el.empty(); $el.append(view.el); util.observeExit($el, function() { util.hide('dialog'); }) $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); }, // ====================================================== // Core Templating template: ' \| {{/notEmpty}} \ {{#headers}} \ | \ \ \ | \ {{/headers}} \
| \
\
\
| \
|||||||||||||||
Traverse and transform objects by visiting every node on a recursive walk using js-traverse.
\| \
\
\
| \
|||||||||||||||