diff --git a/css/bootstrap.css b/css/bootstrap.css new file mode 100644 index 00000000..aad87eca --- /dev/null +++ b/css/bootstrap.css @@ -0,0 +1,9 @@ +body { + padding-top: 60px; +} + +/* we do not have a LH sidebar */ +.container-fluid > .content { + margin-left: 0; +} + diff --git a/css/data-explorer.css b/css/data-explorer.css new file mode 100644 index 00000000..f2ef36d6 --- /dev/null +++ b/css/data-explorer.css @@ -0,0 +1,514 @@ +.data-explorer .header .navigation, +.data-explorer .header .navigation li, +.data-explorer .header .pagination, +.data-explorer .header .pagination form +{ + display: inline; +} + +.data-explorer .header .navigation { + float: left; + margin-left: 0; +} + +.header .pagination { + float: right; + margin: 4px; +} + +.header .pagination label { + float: none; +} + +.header .pagination input { + width: 30px; +} + +.doc-count { + font-weight: bold; + font-size: 120%; +} + +.data-view-container { + display: block; + clear: both; +} + +/* twitter btn.disabled but for button link that is active. used in navigation */ +.active .btn { + cursor: default; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: alpha(opacity=65); + -khtml-opacity: 0.65; + -moz-opacity: 0.65; + opacity: 0.65; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + + +/********************************************************** + * Notifications + *********************************************************/ + +.notification-container { + width: 400px; + left: 520px; + display: none; + position: fixed; + top: 0; + z-index: 100; + text-align: center; +} + +.notification { + display: inline-block; + margin: 0 auto; + padding: 5px 8px 4px; + font-size: 1.3em; + text-align: left; + font-weight: bold; + background: #fe8; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.notification-action { + padding-left: 10px; +} + +.notification-loader { + padding: 0 3px 0 0; + opacity: 0.3; +} + + +/********************************************************** + * Data Table + *********************************************************/ + +/* direct borrowing from twitter buttons */ +.data-table th, +.transform-column-view .expression-preview-table-wrapper th +{ + background-color: #e6e6e6; + background-repeat: no-repeat; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); + background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + color: #333; + border: 1px solid #ccc; + border-bottom-color: #bbb; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -webkit-transition: 0.1s linear all; + -moz-transition: 0.1s linear all; + -ms-transition: 0.1s linear all; + -o-transition: 0.1s linear all; + transition: 0.1s linear all; +} + +.data-table { + border: 1px solid #ccc; + font-size: 12px; +} + +.data-table td, .data-table th { + border-left: 1px solid #ccc; + padding: 3px 4px; +} + +.data-table tr td:first-child, .data-table tr th:first-child { + width: 20px; +} + +/********************************************************** + * Data Table Menus + *********************************************************/ + +a.column-header-menu { + float: right; + display: block; + margin: 0 4px 0 0; + width: 17px; + height: 19px; + background-image: url(images/menu-dropdown.png); + background-repeat: no-repeat; +} + +a.row-header-menu:hover { + background-position: -17px 0px; + text-decoration: none; +} + +a.row-header-menu { + float: left; + display: block; + margin: -2px 0 -4px 0; + width: 17px; + height: 18px; + background-image: url(images/menu-dropdown.png); + background-repeat: no-repeat; +} + +a.column-header-menu:hover { + background-position: -17px 0px; + text-decoration: none; +} + +.column-header-recon-stats-bar { + margin-top: 10px; + height: 4px; + background: #ddd; + border: 1px solid #ccc; + position: relative; + width: 100%; +} + +.column-header-recon-stats-matched { + position: absolute; + height: 100%; + background: #282; +} + +.column-header-recon-stats-blanks { + position: absolute; + height: 100%; + background: #3d3; +} + +div.data-table-cell-content { + line-height: 1.2; + color: #222; + position: relative; +} + +div.data-table-cell-content-numeric { + text-align: right; +} + +a.data-table-cell-edit { + position: absolute; + top: 0; + right: 0; + display: block; + width: 25px; + height: 16px; + text-decoration: none; + background-image: url(images/edit-map.png); + background-repeat: no-repeat; + visibility: hidden; +} + +a.data-table-cell-edit:hover { + background-position: -25px 0px; +} + +.data-table td:hover .data-table-cell-edit { + visibility: visible; +} + +div.data-table-cell-content-numeric > a.data-table-cell-edit { + left: 0px; + right: auto; +} + +.data-table-value-nonstring { + color: #282; +} + +.data-table-error { + color: red; +} + +.data-table-menu-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +ul.data-table-menu { + display: none; + outline-style: none; + background: white; + color: black; + font-size: 12px; + height: auto; + list-style: none; + overflow: hidden; + position: absolute; + text-align: left; + width: 120px; + z-index: 666; + border: 1px solid #CCC; + border-right: 1px solid #666; + border-bottom: 1px solid #666; + margin: 0; padding: 0; } + ul.data-table-menu * { + margin: 0; + padding: 0; } + ul.data-table-menu a { + line-height: 14px; + color: black; + display: block; + padding: 5px 7px; + text-decoration: none; } + ul.data-table-menu li { + height: 24px; } + ul.data-table-menu li:hover { + background-color: #DBE8F8 } + +/* TODO: not sure the rest of this is needed */ +.data-table-cell-editor, .data-table-topic-popup { + overflow: auto; + border: 1px solid #bcf; + background: #e3e9ff; + padding: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} + +.data-table-topic-popup-header { + padding: 0 0 5px; +} + +.data-table-cell-editor-editor { + overflow: hidden; + display: block; + width: 98%; + height: 3em; + font-family: monospace; + margin: 3px 0; +} + +.data-table-cell-copypaste-editor { + overflow: hidden; + display: block; + width: 98%; + height: 10em; + font-family: monospace; + margin: 3px 0; +} + +.data-table-cell-editor-action { + float: left; + vertical-align: bottom; + text-align: center; +} + +.data-table-cell-editor-key { + font-size: 0.8em; + color: #999; +} + +ul.sorting-dialog-blank-error-positions { + margin: 0; + padding: 5px; + height: 10em; + border: 1px solid #ccc; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +ul.sorting-dialog-blank-error-positions > li { + display: block; + border: 1px solid #ccc; + background: #eee; + padding: 5px; + margin: 2px; + cursor: move; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + + +/********************************************************** + * Dialogs + *********************************************************/ + +.dialog-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #666; + opacity: 0.5; +} + +.dialog { + position: fixed; + left: 0; + width: 100%; + text-align: center; +} + +.dialog-frame { + margin: 0 auto; + text-align: left; + background: white; + border: 1px solid #3a5774; +} + +.dialog-border { + border: 4px solid #c1d9ff; +} + +.dialog-header { + background: #e0edfe; + padding: 10px; + font-weight: bold; + font-size: 1.6em; + color: #000; + cursor: move; +} + +.dialog-body { + overflow: auto; + font-size: 1.3em; + padding: 15px; +} + +.dialog-instruction { + padding: 0 0 7px; +} + +.dialog-footer { + font-size: 1.3em; + background: #eee; + padding: 10px; +} + +.dialog-busy { + width: 400px; + border: none; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} + +/********************************************************** + * Transform Dialog + *********************************************************/ + +#expression-preview-tabs .ui-tabs-nav li a { + padding: 0.15em 1em; +} + +textarea.expression-preview-code { + font-family: monospace; + height: 5em; + vertical-align: top; +} + +.expression-preview-parsing-status { + color: #999; +} + +.expression-preview-parsing-status.error { + color: red; +} + +#expression-preview-tabs-preview, +#expression-preview-tabs-help, +#expression-preview-tabs-history, +#expression-preview-tabs-starred { + padding: 5px; + overflow: hidden; +} + +#expression-preview-tabs-preview > div, +#expression-preview-tabs-help > div, +#expression-preview-tabs-history > div, +#expression-preview-tabs-starred { + height: 200px; + overflow: auto; +} + +#expression-preview-tabs-preview td, #expression-preview-tabs-preview th, +#expression-preview-tabs-help td, #expression-preview-tabs-help th, +#expression-preview-tabs-history td, #expression-preview-tabs-history th, +#expression-preview-tabs-starred td, #expression-preview-tabs-starred th { + padding: 5px; +} + +.expression-preview-table-wrapper { + padding: 7px; +} + +.expression-preview-container td { + padding: 2px 5px; + border-top: 1px solid #ccc; +} + +td.expression-preview-heading { + border-top: none; + background: #ddd; + font-weight: bold; +} + +td.expression-preview-value { + max-width: 250px !important; + overflow-x: hidden; +} + +.expression-preview-special-value { + color: #aaa; +} + +.expression-preview-help-container h3 { + margin-top: 15px; + margin-bottom: 7px; + border-bottom: 1px solid #999; +} + +.expression-preview-doc-item-title { + font-weight: bold; + text-align: right; +} + +.expression-preview-doc-item-params { +} + +.expression-preview-doc-item-returns { +} + +.expression-preview-doc-item-desc { + color: #666; +} + + +/********************************************************** + * Read-only mode + *********************************************************/ + +.read-only .data-table tr td:first-child, +.read-only .data-table tr th:first-child +{ + display: none; +} + +.read-only .column-header-menu, +.read-only .row-header-menu, +.read-only a.data-table-cell-edit +{ + display: none; +} + diff --git a/css/graph-flot.css b/css/graph-flot.css new file mode 100644 index 00000000..d50f11e1 --- /dev/null +++ b/css/graph-flot.css @@ -0,0 +1,50 @@ +.data-graph-container .graph { + height: 500px; + margin-right: 200px; +} + +.data-graph-container .legend table { + width: auto; + margin-bottom: 0; +} + +.data-graph-container .legend td { + padding: 5px; + line-height: 13px; +} + +/********************************************************** + * Editor + *********************************************************/ + +.data-graph-container .editor { + float: right; + width: 200px; + padding-left: 0px; +} + +.data-graph-container .editor-info { + padding-left: 4px; +} + +.data-graph-container .editor-info { + cursor: pointer; +} + +.data-graph-container .editor form { + padding-left: 4px; +} + +.data-graph-container .editor select { + width: 100%; +} + +.data-graph-container .editor-info { + border-bottom: 1px solid #ddd; + margin-bottom: 10px; +} + +.data-graph-container .editor-hide-info p { + display: none; +} + diff --git a/css/images/edit-map.png b/css/images/edit-map.png new file mode 100755 index 00000000..dea0ed1e Binary files /dev/null and b/css/images/edit-map.png differ diff --git a/css/images/menu-dropdown.png b/css/images/menu-dropdown.png new file mode 100755 index 00000000..c733fef7 Binary files /dev/null and b/css/images/menu-dropdown.png differ diff --git a/demo/index.html b/demo/index.html old mode 100755 new mode 100644 index ddee99cf..cc345dc4 --- a/demo/index.html +++ b/demo/index.html @@ -1,13 +1,22 @@ - - - - - Data Explorer - - - - + + + + + Recline Data Explorer Demo + + + + + + + + + + + @@ -15,437 +24,26 @@ - -
- - - + diff --git a/demo/js/app.js b/demo/js/app.js index 1bf6b6f6..470f7b34 100755 --- a/demo/js/app.js +++ b/demo/js/app.js @@ -1,12 +1,12 @@ $(function() { // do not like all these window globals ... // window.$container = $('.container .right-panel'); - window.$container = $('.container'); + window.$container = $('.data-explorer-here'); var dataset = demoDataset(); window.dataExplorer = new recline.View.DataExplorer({ - model: dataset + el: window.$container + , model: dataset }); - window.$container.append(window.dataExplorer.el); setupLoadFromWebstore(function(dataset) { window.dataExplorer.remove(); window.dataExplorer = null; @@ -15,6 +15,10 @@ $(function() { }); window.$container.append(window.dataExplorer.el); }); + $('a.set-read-only').click(function() { + window.dataExplorer.setReadOnly(); + alert('Read-only mode set'); + }); }) function demoDataset() { diff --git a/demo/original.html b/demo/original.html new file mode 100755 index 00000000..71cc17e9 --- /dev/null +++ b/demo/original.html @@ -0,0 +1,241 @@ + + + + + Data Explorer + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/style/data-table.css b/demo/style/data-table.css index 39cbcde1..c455ce8b 100755 --- a/demo/style/data-table.css +++ b/demo/style/data-table.css @@ -173,7 +173,7 @@ a img { top: -4px; } -#notification-container { +.notification-container { width: 400px; left: 520px; display: none; @@ -183,7 +183,7 @@ a img { text-align: center; } -#notification { +.notification { display: inline-block; margin: 0 auto; padding: 5px 8px 4px; diff --git a/demo/style/style.css b/demo/style/style.css index 40c295f4..5b725b9d 100755 --- a/demo/style/style.css +++ b/demo/style/style.css @@ -43,7 +43,7 @@ a.button:hover span.icon.loading { background-image: url(images/loader-blue.gif) .chosen {border: 1px solid green} .info { padding: 0px 0px 10px 0px} .large-loader { position: relative; } -.menu-overlay { +.data-table-menu-overlay { position: fixed; top: 0; left: 0; @@ -51,7 +51,7 @@ a.button:hover span.icon.loading { background-image: url(images/loader-blue.gif) height: 100%; } -ul.menu { +ul.data-table-menu { display: none; outline-style: none; background: white; @@ -68,18 +68,18 @@ ul.menu { border-right: 1px solid #666; border-bottom: 1px solid #666; margin: 0; padding: 0; } - ul.menu * { + ul.data-table-menu * { margin: 0; padding: 0; } - ul.menu a { + ul.data-table-menu a { line-height: 14px; color: black; display: block; padding: 5px 7px; text-decoration: none; } - ul.menu li { + ul.data-table-menu li { height: 24px; } - ul.menu li:hover { + ul.data-table-menu li:hover { background-color: #DBE8F8 } @@ -213,7 +213,7 @@ span.tooltip-status { /* rgrp added mods */ -.data-explorer .nav { +.data-explorer .header { border-top: 15px solid #BCF; border-left: 5px solid #BCF; display: block; @@ -227,6 +227,27 @@ span.tooltip-status { padding-top: 3px; } +.header .navigation, +.header .navigation li, +.header .pagination, +.header .pagination form +{ + display: inline; +} + +.header .pagination { + float: right; +} + +.header .pagination input { + width: 40px; +} + +.doc-count { + font-weight: bold; + font-size: 120%; +} + .data-view-container { display: block; border-top: 1px solid #BCF; @@ -239,13 +260,3 @@ span.tooltip-status { right: 0px; background: white; } - -.nav-pagination { - display: inline; - float: right; - list-style-type: none; -} - -.nav-pagination input { - width: 40px; -} diff --git a/src/costco.js b/src/costco.js index edce7590..2256b3c3 100755 --- a/src/costco.js +++ b/src/costco.js @@ -25,8 +25,7 @@ var costco = function() { preview.push({before: JSON.stringify(before), after: JSON.stringify(after)}); } } - // TODO: 2012-01-05 Move this out of this function and up into (view) functions that call this - util.render('editPreview', 'expression-preview-container', {rows: preview}); + return preview; } function mapDocs(docs, editFunc) { diff --git a/src/model.js b/src/model.js index ba143881..e102063b 100644 --- a/src/model.js +++ b/src/model.js @@ -6,26 +6,32 @@ recline.Model = function($) { var my = {}; // A Dataset model. +// +// Other than standard list of Backbone attributes it has two important attributes: +// +// * currentDocuments: a DocumentList containing the Documents we have currently loaded for viewing (you update currentDocuments by calling getRows) +// * docCount: total number of documents in this dataset (obtained on a fetch for this Dataset) my.Dataset = Backbone.Model.extend({ __type__: 'Dataset', initialize: function() { this.currentDocuments = new my.DocumentList(); + this.docCount = null; }, - getLength: function() { - return this.rowCount; - }, - - // Get rows (documents) from the backend returning a recline.DocumentList + // AJAX method with promise API to get rows (documents) from the backend. // - // TODO: ? rename to getDocuments? + // Resulting DocumentList are used to reset this.currentDocuments and are + // also returned. + // + // :param numRows: passed onto backend getDocuments. + // :param start: passed onto backend getDocuments. // // this does not fit very well with Backbone setup. Backbone really expects you to know the ids of objects your are fetching (which you do in classic RESTful ajax-y world). But this paradigm does not fill well with data set up we have here. // This also illustrates the limitations of separating the Dataset and the Backend - getRows: function(numRows, start) { + getDocuments: function(numRows, start) { var self = this; var dfd = $.Deferred(); - this.backend.getRows(this.id, numRows, start).then(function(rows) { + this.backend.getDocuments(this.id, numRows, start).then(function(rows) { var docs = _.map(rows, function(row) { return new my.Document(row); }); @@ -33,6 +39,12 @@ my.Dataset = Backbone.Model.extend({ dfd.resolve(self.currentDocuments); }); return dfd.promise(); + }, + + toTemplateJSON: function() { + var data = this.toJSON(); + data.docCount = this.docCount; + return data; } }); @@ -55,14 +67,16 @@ my.setBackend = function(backend) { // Backend which just caches in memory // -// Does not need to be a backbone model but provides some conveience +// Does not need to be a backbone model but provides some conveniences my.BackendMemory = Backbone.Model.extend({ // Initialize a Backend with a local in-memory dataset. // // NB: We can handle one and only one dataset at a time. // // :param dataset: the data for a dataset on which operations will be - // performed. In the form of a hash with metadata and data attributes. + // performed. Its form should be a hash with metadata and data + // attributes. + // // - metadata: hash of key/value attributes of any kind (but usually with title attribute) // - data: hash with 2 keys: // - headers: list of header names/labels @@ -70,13 +84,13 @@ my.BackendMemory = Backbone.Model.extend({ // // Example of data: // - // { - // headers: ['x', 'y', 'z'] - // , rows: [ - // {id: 0, x: 1, y: 2, z: 3} - // , {id: 1, x: 2, y: 4, z: 6} - // ] - // }; + // { + // headers: ['x', 'y', 'z'] + // , rows: [ + // {id: 0, x: 1, y: 2, z: 3} + // , {id: 1, x: 2, y: 4, z: 6} + // ] + // }; initialize: function(dataset) { // deep copy this._datasetAsData = $.extend(true, {}, dataset); @@ -103,7 +117,7 @@ my.BackendMemory = Backbone.Model.extend({ dataset.set({ headers: rawDataset.data.headers }); - dataset.rowCount = rawDataset.data.rows.length; + dataset.docCount = rawDataset.data.rows.length; dfd.resolve(dataset); } return dfd.promise(); @@ -131,7 +145,7 @@ my.BackendMemory = Backbone.Model.extend({ alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model); } }, - getRows: function(datasetId, numRows, start) { + getDocuments: function(datasetId, numRows, start) { if (start === undefined) { start = 0; } @@ -184,14 +198,14 @@ my.BackendWebstore = Backbone.Model.extend({ dataset.set({ headers: headers }); - dataset.rowCount = schema.count; + dataset.docCount = schema.count; dfd.resolve(dataset, jqxhr); }); return dfd.promise(); } } }, - getRows: function(datasetId, numRows, start) { + getDocuments: function(datasetId, numRows, start) { if (start === undefined) { start = 0; } diff --git a/src/util.js b/src/util.js index 009284c4..97e5c82d 100755 --- a/src/util.js +++ b/src/util.js @@ -1,4 +1,51 @@ var util = function() { + var templates = { + transformActions: '
  • Global transform...
  • ' + , columnActions: ' \ +
  • Transform...
  • \ +
  • Delete this column
  • \ + ' + , rowActions: '
  • Delete this row
  • ' + , cellEditor: ' \ + \ + ' + , editPreview: ' \ +
    \ + \ + \ + \ + \ + \ + \ + \ + \ + {{#rows}} \ + \ + \ + \ + \ + {{/rows}} \ + \ +
    \ + before \ + \ + after \ +
    \ + {{before}} \ + \ + {{after}} \ +
    \ +
    \ + ' + }; $.fn.serializeObject = function() { var o = {}; @@ -74,7 +121,7 @@ var util = function() { } function position( thing, elem, offset ) { - var position = $(elem.target).offset(); + var position = $(elem.target).position(); if (offset) { if (offset.top) position.top += offset.top; if (offset.left) position.left += offset.left; @@ -89,7 +136,7 @@ var util = function() { function render( template, target, options ) { if ( !options ) options = {data: {}}; if ( !options.data ) options = {data: options}; - var html = $.mustache( $( "." + template + "Template:first" ).html(), options.data ); + var html = $.mustache( templates[template], options.data ); if (target instanceof jQuery) { var targetDom = target; } else { @@ -103,16 +150,31 @@ var util = function() { // TODO: remove (commented out as part of Backbon-i-fication // if (template in app.after) app.after[template](); } - - function notify( message, options ) { - if (!options) var options = {}; - $('#notification-container').show(); - $('#notification-message').text(message); - if (!options.loader) $('.notification-loader').hide(); - if (options.loader) $('.notification-loader').show(); - if (!options.persist) setTimeout(function() { $('#notification-container').hide() }, 3000); - } + function notify(message, options) { + if (!options) var options = {}; + var tmplData = _.extend({ + msg: message, + category: 'warning' + }, + options); + var _template = ' \ +
    × \ +

    {{msg}} \ + {{#loader}} \ + \ + {{/loader}} \ +

    \ +
    '; + var _templated = $.mustache(_template, tmplData); + _templated = $(_templated).appendTo($('.data-explorer .alert-messages')); + if (!options.persist) { + setTimeout(function() { + $(_templated).remove(); + }, 3000); + } + } + function formatMetadata(data) { out = '
    '; $.each(data, function(key, val) { diff --git a/src/view.js b/src/view.js index e416d697..025e6f3d 100644 --- a/src/view.js +++ b/src/view.js @@ -5,86 +5,164 @@ recline.View = function($) { var my = {}; +// Parse a URL query string (?xyz=abc...) into a dictionary. +function parseQueryString(q) { + var urlParams = {}, + e, d = function (s) { + return unescape(s.replace(/\+/g, " ")); + }, + r = /([^&=]+)=?([^&]*)/g; + + if (q && q.length && q[0] === '?') { + q = q.slice(1); + } + while (e = r.exec(q)) { + // TODO: have values be array as query string allow repetition of keys + urlParams[d(e[1])] = d(e[2]); + } + return urlParams; +} + +// 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({ - tagName: 'div', - className: 'data-explorer', template: ' \ -