Merge branch 'master' into gh-pages
This commit is contained in:
commit
7003338c9d
@ -16,7 +16,7 @@ Live demo: http://okfnlabs.org/recline/demo/
|
|||||||
* Bulk update/clean your data using an easy scripting UI
|
* Bulk update/clean your data using an easy scripting UI
|
||||||
* Visualize your data
|
* Visualize your data
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Demo App
|
## Demo App
|
||||||
|
|||||||
@ -150,7 +150,7 @@
|
|||||||
* Data Table Menus
|
* Data Table Menus
|
||||||
*********************************************************/
|
*********************************************************/
|
||||||
|
|
||||||
a.column-header-menu {
|
a.column-header-menu, a.root-header-menu {
|
||||||
float: right;
|
float: right;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 4px 0 0;
|
margin: 0 4px 0 0;
|
||||||
@ -160,7 +160,7 @@ a.column-header-menu {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.row-header-menu:hover {
|
a.row-header-menu:hover, a.root-header-menu:hover {
|
||||||
background-position: -17px 0px;
|
background-position: -17px 0px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@ -175,6 +175,10 @@ a.row-header-menu {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.read-only a.row-header-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
a.column-header-menu:hover {
|
a.column-header-menu:hover {
|
||||||
background-position: -17px 0px;
|
background-position: -17px 0px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -511,14 +515,14 @@ td.expression-preview-value {
|
|||||||
* Read-only mode
|
* Read-only mode
|
||||||
*********************************************************/
|
*********************************************************/
|
||||||
|
|
||||||
.read-only .data-table tr td:first-child,
|
/*.read-only .data-table tr td:first-child,
|
||||||
.read-only .data-table tr th:first-child
|
.read-only .data-table tr th:first-child
|
||||||
{
|
{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
.read-only .column-header-menu,
|
.read-only .write-op,
|
||||||
.read-only .row-header-menu,
|
|
||||||
.read-only a.data-table-cell-edit
|
.read-only a.data-table-cell-edit
|
||||||
{
|
{
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
13
index.html
13
index.html
@ -84,15 +84,24 @@
|
|||||||
<li><a href="demo/index.html">Demo</a></li>
|
<li><a href="demo/index.html">Demo</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="downloads">Downloads & Dependencies <small>(Right-click, and use 'Save As')</small></h2>
|
||||||
|
<p><a href="recline.js" class="btn">Development Version (v0.2)</a></p>
|
||||||
|
|
||||||
|
<h2 id="using">Using It</h2>
|
||||||
|
<p>Check out the the <a href="demo/">Demo</a> and view source. The
|
||||||
|
javascript you want for actual integration is in: <a
|
||||||
|
href="demo/js/app.js">app.js</a>.</p>
|
||||||
|
|
||||||
<h2 id="docs">Docs</h2>
|
<h2 id="docs">Docs</h2>
|
||||||
<p>Want to see how to embed this in your own application. Check out the the
|
|
||||||
<a href="demo/">Demo</a> and view source.</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="docs/model.html">Models</a></li>
|
<li><a href="docs/model.html">Models</a></li>
|
||||||
<li><a href="docs/backend.html">Backends</a></li>
|
<li><a href="docs/backend.html">Backends</a></li>
|
||||||
<li><a href="docs/view.html">Views including the main Data Explorer</a></li>
|
<li><a href="docs/view.html">Views including the main Data Explorer</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="tests">Tests</h2>
|
||||||
|
<p><a href="test/index.html">Run the tests online</a>.</p>
|
||||||
|
|
||||||
<h2 id="history">History</h2>
|
<h2 id="history">History</h2>
|
||||||
<p>Max Ogden was developing Recline as the frontend data browser and editor
|
<p>Max Ogden was developing Recline as the frontend data browser and editor
|
||||||
for his <a href="http://datacouch.com/">http://datacouch.com/</a> project.
|
for his <a href="http://datacouch.com/">http://datacouch.com/</a> project.
|
||||||
|
|||||||
1680
recline.js
Normal file
1680
recline.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,10 +11,38 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
(function($, my) {
|
(function($, my) {
|
||||||
my.backends = {};
|
my.backends = {};
|
||||||
|
|
||||||
|
// ## Backbone.sync
|
||||||
|
//
|
||||||
|
// Override Backbone.sync to hand off to sync function in relevant backend
|
||||||
Backbone.sync = function(method, model, options) {
|
Backbone.sync = function(method, model, options) {
|
||||||
return my.backends[model.backendConfig.type].sync(method, model, options);
|
return my.backends[model.backendConfig.type].sync(method, model, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ## wrapInTimeout
|
||||||
|
//
|
||||||
|
// Crude way to catch backend errors
|
||||||
|
// Many of backends use JSONP and so will not get error messages and this is
|
||||||
|
// a crude way to catch those errors.
|
||||||
|
function wrapInTimeout(ourFunction) {
|
||||||
|
var dfd = $.Deferred();
|
||||||
|
var timeout = 5000;
|
||||||
|
var timer = setTimeout(function() {
|
||||||
|
dfd.reject({
|
||||||
|
message: 'Request Error: Backend did not respond after ' + (timeout / 1000) + ' seconds'
|
||||||
|
});
|
||||||
|
}, timeout);
|
||||||
|
ourFunction.done(function(arguments) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
dfd.resolve(arguments);
|
||||||
|
})
|
||||||
|
.fail(function(arguments) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
dfd.reject(arguments);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
return dfd.promise();
|
||||||
|
}
|
||||||
|
|
||||||
// ## BackendMemory - uses in-memory data
|
// ## BackendMemory - uses in-memory data
|
||||||
//
|
//
|
||||||
// To use you should:
|
// To use you should:
|
||||||
@ -78,16 +106,19 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
|
alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDocuments: function(model, numRows, start) {
|
query: function(model, queryObj) {
|
||||||
if (start === undefined) {
|
var numRows = queryObj.size;
|
||||||
start = 0;
|
var start = queryObj.offset;
|
||||||
}
|
|
||||||
if (numRows === undefined) {
|
|
||||||
numRows = 10;
|
|
||||||
}
|
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
rows = model.backendConfig.data.rows;
|
results = model.backendConfig.data.rows;
|
||||||
var results = rows.slice(start, start+numRows);
|
// not complete sorting!
|
||||||
|
_.each(queryObj.sort, function(item) {
|
||||||
|
results = _.sortBy(results, function(row) {
|
||||||
|
var _out = row[item[0]];
|
||||||
|
return (item[1] == 'asc') ? _out : -1*_out;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var results = results.slice(start, start+numRows);
|
||||||
dfd.resolve(results);
|
dfd.resolve(results);
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
}
|
}
|
||||||
@ -119,7 +150,7 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
jsonp: '_callback'
|
jsonp: '_callback'
|
||||||
});
|
});
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
jqxhr.then(function(schema) {
|
wrapInTimeout(jqxhr).done(function(schema) {
|
||||||
headers = _.map(schema.data, function(item) {
|
headers = _.map(schema.data, function(item) {
|
||||||
return item.name;
|
return item.name;
|
||||||
});
|
});
|
||||||
@ -128,27 +159,29 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
});
|
});
|
||||||
dataset.docCount = schema.count;
|
dataset.docCount = schema.count;
|
||||||
dfd.resolve(dataset, jqxhr);
|
dfd.resolve(dataset, jqxhr);
|
||||||
|
})
|
||||||
|
.fail(function(arguments) {
|
||||||
|
dfd.reject(arguments);
|
||||||
});
|
});
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDocuments: function(model, numRows, start) {
|
query: function(model, queryObj) {
|
||||||
if (start === undefined) {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
if (numRows === undefined) {
|
|
||||||
numRows = 10;
|
|
||||||
}
|
|
||||||
var base = model.backendConfig.url;
|
var base = model.backendConfig.url;
|
||||||
|
var data = {
|
||||||
|
_limit: queryObj.size
|
||||||
|
, _offset: queryObj.offset
|
||||||
|
};
|
||||||
var jqxhr = $.ajax({
|
var jqxhr = $.ajax({
|
||||||
url: base + '.json?_limit=' + numRows,
|
url: base + '.json',
|
||||||
dataType: 'jsonp',
|
data: data,
|
||||||
jsonp: '_callback',
|
dataType: 'jsonp',
|
||||||
cache: true
|
jsonp: '_callback',
|
||||||
|
cache: true
|
||||||
});
|
});
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
jqxhr.then(function(results) {
|
jqxhr.done(function(results) {
|
||||||
dfd.resolve(results.data);
|
dfd.resolve(results.data);
|
||||||
});
|
});
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
@ -194,11 +227,14 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
, dataType: 'jsonp'
|
, dataType: 'jsonp'
|
||||||
});
|
});
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
jqxhr.then(function(results) {
|
wrapInTimeout(jqxhr).done(function(results) {
|
||||||
dataset.set({
|
dataset.set({
|
||||||
headers: results.fields
|
headers: results.fields
|
||||||
});
|
});
|
||||||
dfd.resolve(dataset, jqxhr);
|
dfd.resolve(dataset, jqxhr);
|
||||||
|
})
|
||||||
|
.fail(function(arguments) {
|
||||||
|
dfd.reject(arguments);
|
||||||
});
|
});
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
}
|
}
|
||||||
@ -206,17 +242,11 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
alert('This backend only supports read operations');
|
alert('This backend only supports read operations');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDocuments: function(dataset, numRows, start) {
|
query: function(dataset, queryObj) {
|
||||||
if (start === undefined) {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
if (numRows === undefined) {
|
|
||||||
numRows = 10;
|
|
||||||
}
|
|
||||||
var base = my.backends['dataproxy'].get('dataproxy');
|
var base = my.backends['dataproxy'].get('dataproxy');
|
||||||
var data = {
|
var data = {
|
||||||
url: dataset.backendConfig.url
|
url: dataset.backendConfig.url
|
||||||
, 'max-results': numRows
|
, 'max-results': queryObj.size
|
||||||
, type: dataset.backendConfig.format
|
, type: dataset.backendConfig.format
|
||||||
};
|
};
|
||||||
var jqxhr = $.ajax({
|
var jqxhr = $.ajax({
|
||||||
@ -225,7 +255,7 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
, dataType: 'jsonp'
|
, dataType: 'jsonp'
|
||||||
});
|
});
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
jqxhr.then(function(results) {
|
jqxhr.done(function(results) {
|
||||||
var _out = _.map(results.data, function(row) {
|
var _out = _.map(results.data, function(row) {
|
||||||
var tmp = {};
|
var tmp = {};
|
||||||
_.each(results.fields, function(key, idx) {
|
_.each(results.fields, function(key, idx) {
|
||||||
@ -260,7 +290,7 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
return dfd.promise(); }
|
return dfd.promise(); }
|
||||||
},
|
},
|
||||||
|
|
||||||
getDocuments: function(dataset, start, numRows) {
|
query: function(dataset, queryObj) {
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
var fields = dataset.get('headers');
|
var fields = dataset.get('headers');
|
||||||
|
|
||||||
|
|||||||
103
src/costco.js
103
src/costco.js
@ -60,110 +60,9 @@ var costco = function() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDocs(editFunc) {
|
|
||||||
var dfd = $.Deferred();
|
|
||||||
util.notify("Download entire database into Recline. This could take a while...", {persist: true, loader: true});
|
|
||||||
couch.request({url: app.baseURL + "api/json"}).then(function(docs) {
|
|
||||||
util.notify("Updating " + docs.docs.length + " documents. This could take a while...", {persist: true, loader: true});
|
|
||||||
var toUpdate = costco.mapDocs(docs.docs, editFunc).edited;
|
|
||||||
costco.uploadDocs(toUpdate).then(
|
|
||||||
function(updatedDocs) {
|
|
||||||
util.notify(updatedDocs.length + " documents updated successfully");
|
|
||||||
recline.initializeTable(app.offset);
|
|
||||||
dfd.resolve(updatedDocs);
|
|
||||||
},
|
|
||||||
function(err) {
|
|
||||||
util.notify("Errorz! " + err);
|
|
||||||
dfd.reject(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return dfd.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDoc(doc) {
|
|
||||||
return couch.request({type: "PUT", url: app.baseURL + "api/" + doc._id, data: JSON.stringify(doc)})
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadDocs(docs) {
|
|
||||||
var dfd = $.Deferred();
|
|
||||||
if(!docs.length) dfd.resolve("Failed: No docs specified");
|
|
||||||
couch.request({url: app.baseURL + "api/_bulk_docs", type: "POST", data: JSON.stringify({docs: docs})})
|
|
||||||
.then(
|
|
||||||
function(resp) {ensureCommit().then(function() {
|
|
||||||
var error = couch.responseError(resp);
|
|
||||||
if (error) {
|
|
||||||
dfd.reject(error);
|
|
||||||
} else {
|
|
||||||
dfd.resolve(resp);
|
|
||||||
}
|
|
||||||
})},
|
|
||||||
function(err) { dfd.reject(err.responseText) }
|
|
||||||
);
|
|
||||||
return dfd.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureCommit() {
|
|
||||||
return couch.request({url: app.baseURL + "api/_ensure_full_commit", type:'POST', data: "''"});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteColumn(name) {
|
|
||||||
var deleteFunc = function(doc) {
|
|
||||||
delete doc[name];
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
return updateDocs(deleteFunc);
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadCSV() {
|
|
||||||
var file = $('#file')[0].files[0];
|
|
||||||
if (file) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.readAsText(file);
|
|
||||||
reader.onload = function(event) {
|
|
||||||
var payload = {
|
|
||||||
url: window.location.href + "/api/_bulk_docs", // todo more robust url composition
|
|
||||||
data: event.target.result
|
|
||||||
};
|
|
||||||
var worker = new Worker('script/costco-csv-worker.js');
|
|
||||||
worker.onmessage = function(event) {
|
|
||||||
var message = event.data;
|
|
||||||
if (message.done) {
|
|
||||||
var error = couch.responseError(JSON.parse(message.response))
|
|
||||||
console.log('e',error)
|
|
||||||
if (error) {
|
|
||||||
app.emitter.emit(error, 'error');
|
|
||||||
} else {
|
|
||||||
util.notify("Data uploaded successfully!");
|
|
||||||
recline.initializeTable(app.offset);
|
|
||||||
}
|
|
||||||
util.hide('dialog');
|
|
||||||
} else if (message.percent) {
|
|
||||||
if (message.percent === 100) {
|
|
||||||
util.notify("Waiting for CouchDB...", {persist: true, loader: true})
|
|
||||||
} else {
|
|
||||||
util.notify("Uploading... " + message.percent + "%");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
util.notify(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
worker.postMessage(payload);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
util.notify('File not selected. Please try again');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
evalFunction: evalFunction,
|
evalFunction: evalFunction,
|
||||||
previewTransform: previewTransform,
|
previewTransform: previewTransform,
|
||||||
mapDocs: mapDocs,
|
mapDocs: mapDocs
|
||||||
updateDocs: updateDocs,
|
|
||||||
updateDoc: updateDoc,
|
|
||||||
uploadDocs: uploadDocs,
|
|
||||||
deleteColumn: deleteColumn,
|
|
||||||
ensureCommit: ensureCommit,
|
|
||||||
uploadCSV: uploadCSV
|
|
||||||
};
|
};
|
||||||
}();
|
}();
|
||||||
|
|||||||
14
src/model.js
14
src/model.js
@ -15,6 +15,11 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
this.currentDocuments = new my.DocumentList();
|
this.currentDocuments = new my.DocumentList();
|
||||||
this.docCount = null;
|
this.docCount = null;
|
||||||
this.backend = null;
|
this.backend = null;
|
||||||
|
this.defaultQuery = {
|
||||||
|
size: 100
|
||||||
|
, offset: 0
|
||||||
|
};
|
||||||
|
// this.queryState = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### getDocuments
|
// ### getDocuments
|
||||||
@ -29,11 +34,13 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
//
|
//
|
||||||
// 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 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
|
// This also illustrates the limitations of separating the Dataset and the Backend
|
||||||
getDocuments: function(numRows, start) {
|
query: function(queryObj) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var backend = my.backends[this.backendConfig.type];
|
var backend = my.backends[this.backendConfig.type];
|
||||||
|
this.queryState = queryObj || this.defaultQuery;
|
||||||
|
this.queryState = _.extend({size: 100, offset: 0}, this.queryState);
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
backend.getDocuments(this, numRows, start).then(function(rows) {
|
backend.query(this, this.queryState).done(function(rows) {
|
||||||
var docs = _.map(rows, function(row) {
|
var docs = _.map(rows, function(row) {
|
||||||
var _doc = new my.Document(row);
|
var _doc = new my.Document(row);
|
||||||
_doc.backendConfig = self.backendConfig;
|
_doc.backendConfig = self.backendConfig;
|
||||||
@ -42,6 +49,9 @@ this.recline.Model = this.recline.Model || {};
|
|||||||
});
|
});
|
||||||
self.currentDocuments.reset(docs);
|
self.currentDocuments.reset(docs);
|
||||||
dfd.resolve(self.currentDocuments);
|
dfd.resolve(self.currentDocuments);
|
||||||
|
})
|
||||||
|
.fail(function(arguments) {
|
||||||
|
dfd.reject(arguments);
|
||||||
});
|
});
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
},
|
},
|
||||||
|
|||||||
305
src/util.js
305
src/util.js
@ -2,10 +2,17 @@ var util = function() {
|
|||||||
var templates = {
|
var templates = {
|
||||||
transformActions: '<li><a data-action="transform" class="menuAction" href="JavaScript:void(0);">Global transform...</a></li>'
|
transformActions: '<li><a data-action="transform" class="menuAction" href="JavaScript:void(0);">Global transform...</a></li>'
|
||||||
, columnActions: ' \
|
, columnActions: ' \
|
||||||
<li><a data-action="bulkEdit" class="menuAction" href="JavaScript:void(0);">Transform...</a></li> \
|
<li class="write-op"><a data-action="bulkEdit" class="menuAction" href="JavaScript:void(0);">Transform...</a></li> \
|
||||||
<li><a data-action="deleteColumn" class="menuAction" href="JavaScript:void(0);">Delete this column</a></li> \
|
<li class="write-op"><a data-action="deleteColumn" class="menuAction" href="JavaScript:void(0);">Delete this column</a></li> \
|
||||||
|
<li><a data-action="sortAsc" class="menuAction" href="JavaScript:void(0);">Sort ascending</a></li> \
|
||||||
|
<li><a data-action="sortDesc" class="menuAction" href="JavaScript:void(0);">Sort descending</a></li> \
|
||||||
|
<li><a data-action="hideColumn" class="menuAction" href="JavaScript:void(0);">Hide this column</a></li> \
|
||||||
'
|
'
|
||||||
, rowActions: '<li><a data-action="deleteRow" class="menuAction" href="JavaScript:void(0);">Delete this row</a></li>'
|
, rowActions: '<li><a data-action="deleteRow" class="menuAction write-op" href="JavaScript:void(0);">Delete this row</a></li>'
|
||||||
|
, rootActions: ' \
|
||||||
|
{{#columns}} \
|
||||||
|
<li><a data-action="showColumn" data-column="{{.}}" class="menuAction" href="JavaScript:void(0);">Add column: {{.}}</a></li> \
|
||||||
|
{{/columns}}'
|
||||||
, cellEditor: ' \
|
, cellEditor: ' \
|
||||||
<div class="menu-container data-table-cell-editor"> \
|
<div class="menu-container data-table-cell-editor"> \
|
||||||
<textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
|
<textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
|
||||||
@ -63,14 +70,6 @@ var util = function() {
|
|||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
|
||||||
function inURL(url, str) {
|
|
||||||
var exists = false;
|
|
||||||
if ( url.indexOf( str ) > -1 ) {
|
|
||||||
exists = true;
|
|
||||||
}
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerEmitter() {
|
function registerEmitter() {
|
||||||
var Emitter = function(obj) {
|
var Emitter = function(obj) {
|
||||||
this.emit = function(obj, channel) {
|
this.emit = function(obj, channel) {
|
||||||
@ -151,295 +150,13 @@ var util = function() {
|
|||||||
// if (template in app.after) app.after[template]();
|
// if (template in app.after) app.after[template]();
|
||||||
}
|
}
|
||||||
|
|
||||||
function notify(message, options) {
|
|
||||||
if (!options) var options = {};
|
|
||||||
var tmplData = _.extend({
|
|
||||||
msg: message,
|
|
||||||
category: 'warning'
|
|
||||||
},
|
|
||||||
options);
|
|
||||||
var _template = ' \
|
|
||||||
<div class="alert-message {{category}} fade in" data-alert="alert"><a class="close" href="#">×</a> \
|
|
||||||
<p>{{msg}} \
|
|
||||||
{{#loader}} \
|
|
||||||
<img src="images/small-spinner.gif" class="notification-loader"> \
|
|
||||||
{{/loader}} \
|
|
||||||
</p> \
|
|
||||||
</div>';
|
|
||||||
var _templated = $.mustache(_template, tmplData);
|
|
||||||
_templated = $(_templated).appendTo($('.data-explorer .alert-messages'));
|
|
||||||
if (!options.persist) {
|
|
||||||
setTimeout(function() {
|
|
||||||
$(_templated).remove();
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMetadata(data) {
|
|
||||||
out = '<dl>';
|
|
||||||
$.each(data, function(key, val) {
|
|
||||||
if (typeof(val) == 'string' && key[0] != '_') {
|
|
||||||
out = out + '<dt>' + key + '<dd>' + val;
|
|
||||||
} else if (typeof(val) == 'object' && key != "geometry" && val != null) {
|
|
||||||
if (key == 'properties') {
|
|
||||||
$.each(val, function(attr, value){
|
|
||||||
out = out + '<dt>' + attr + '<dd>' + value;
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
out = out + '<dt>' + key + '<dd>' + val.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out = out + '</dl>';
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBaseURL(url) {
|
|
||||||
var baseURL = "";
|
|
||||||
if ( inURL(url, '_design') ) {
|
|
||||||
if (inURL(url, '_rewrite')) {
|
|
||||||
var path = url.split("#")[0];
|
|
||||||
if (path[path.length - 1] === "/") {
|
|
||||||
baseURL = "";
|
|
||||||
} else {
|
|
||||||
baseURL = '_rewrite/';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
baseURL = '_rewrite/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return baseURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
var persist = {
|
|
||||||
restore: function() {
|
|
||||||
$('.persist').each(function(i, el) {
|
|
||||||
var inputId = $(el).attr('id');
|
|
||||||
if(localStorage.getItem(inputId)) $('#' + inputId).val(localStorage.getItem(inputId));
|
|
||||||
})
|
|
||||||
},
|
|
||||||
save: function(id) {
|
|
||||||
localStorage.setItem(id, $('#' + id).val());
|
|
||||||
},
|
|
||||||
clear: function() {
|
|
||||||
$('.persist').each(function(i, el) {
|
|
||||||
localStorage.removeItem($(el).attr('id'));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// simple debounce adapted from underscore.js
|
|
||||||
function delay(func, wait) {
|
|
||||||
return function() {
|
|
||||||
var context = this, args = arguments;
|
|
||||||
var throttler = function() {
|
|
||||||
delete app.timeout;
|
|
||||||
func.apply(context, args);
|
|
||||||
};
|
|
||||||
if (!app.timeout) app.timeout = setTimeout(throttler, wait);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function resetForm(form) {
|
|
||||||
$(':input', form)
|
|
||||||
.not(':button, :submit, :reset, :hidden')
|
|
||||||
.val('')
|
|
||||||
.removeAttr('checked')
|
|
||||||
.removeAttr('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
function largestWidth(selector, min) {
|
|
||||||
var min_width = min || 0;
|
|
||||||
$(selector).each(function(i, n){
|
|
||||||
var this_width = $(n).width();
|
|
||||||
if (this_width > min_width) {
|
|
||||||
min_width = this_width;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return min_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getType(obj) {
|
|
||||||
if (obj === null) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
if (typeof obj === 'object') {
|
|
||||||
if (obj.constructor.toString().indexOf("Array") !== -1) {
|
|
||||||
return 'array';
|
|
||||||
} else {
|
|
||||||
return 'object';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return typeof obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lookupPath(path) {
|
|
||||||
var docs = app.apiDocs;
|
|
||||||
try {
|
|
||||||
_.each(path, function(node) {
|
|
||||||
docs = docs[node];
|
|
||||||
})
|
|
||||||
} catch(e) {
|
|
||||||
util.notify("Error selecting documents" + e);
|
|
||||||
docs = [];
|
|
||||||
}
|
|
||||||
return docs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodePath(docField) {
|
|
||||||
if (docField.children('.object-key').length > 0) return docField.children('.object-key').text();
|
|
||||||
if (docField.children('.array-key').length > 0) return docField.children('.array-key').text();
|
|
||||||
if (docField.children('.doc-key').length > 0) return docField.children('.doc-key').text();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedTreePath() {
|
|
||||||
var nodes = []
|
|
||||||
, parent = $('.chosen');
|
|
||||||
while (parent.length > 0) {
|
|
||||||
nodes.push(nodePath(parent));
|
|
||||||
parent = parent.parents('.doc-field:first');
|
|
||||||
}
|
|
||||||
return _.compact(nodes).reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO refactor handlers so that they dont stack up as the tree gets bigger
|
|
||||||
function handleTreeClick(e) {
|
|
||||||
var clicked = $(e.target);
|
|
||||||
if(clicked.hasClass('expand')) return;
|
|
||||||
if (clicked.children('.array').length > 0) {
|
|
||||||
var field = clicked;
|
|
||||||
} else if (clicked.siblings('.array').length > 0) {
|
|
||||||
var field = clicked.parents('.doc-field:first');
|
|
||||||
} else {
|
|
||||||
var field = clicked.parents('.array').parents('.doc-field:first');
|
|
||||||
}
|
|
||||||
$('.chosen').removeClass('chosen');
|
|
||||||
field.addClass('chosen');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var createTreeNode = {
|
|
||||||
"string": function (obj, key) {
|
|
||||||
var val = $('<div class="doc-value string-type"></div>');
|
|
||||||
if (obj[key].length > 45) {
|
|
||||||
val.append($('<span class="string-type"></span>')
|
|
||||||
.text(obj[key].slice(0, 45)))
|
|
||||||
.append(
|
|
||||||
$('<span class="expand">...</span>')
|
|
||||||
.click(function () {
|
|
||||||
val.html('')
|
|
||||||
.append($('<span class="string-type"></span>')
|
|
||||||
.text(obj[key].length ? obj[key] : " ")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var val = $('<div class="doc-value string-type"></div>');
|
|
||||||
val.append(
|
|
||||||
$('<span class="string-type"></span>')
|
|
||||||
.text(obj[key].length ? obj[key] : " ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
, "number": function (obj, key) {
|
|
||||||
var val = $('<div class="doc-value number"></div>')
|
|
||||||
val.append($('<span class="number-type">' + obj[key] + '</span>'))
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
, "null": function (obj, key) {
|
|
||||||
var val = $('<div class="doc-value null"></div>')
|
|
||||||
val.append($('<span class="null-type">' + obj[key] + '</span>'))
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
, "boolean": function (obj, key) {
|
|
||||||
var val = $('<div class="fue null"></div>')
|
|
||||||
val.append($('<span class="null-type">' + obj[key] + '</span>'))
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
, "array": function (obj, key, indent) {
|
|
||||||
if (!indent) indent = 1;
|
|
||||||
var val = $('<div class="doc-value array"></div>')
|
|
||||||
$('<span class="array-type">[</span><span class="expand" style="float:left">...</span><span class="array-type">]</span>')
|
|
||||||
.click(function (e) {
|
|
||||||
var n = $(this).parent();
|
|
||||||
var cls = 'sub-'+key+'-'+indent
|
|
||||||
n.html('')
|
|
||||||
n.append('<span style="padding-left:'+((indent - 1) * 10)+'px" class="array-type">[</span>')
|
|
||||||
for (i in obj[key]) {
|
|
||||||
var field = $('<div class="doc-field"></div>').click(handleTreeClick);
|
|
||||||
n.append(
|
|
||||||
field
|
|
||||||
.append('<div class="array-key '+cls+'" >'+i+'</div>')
|
|
||||||
.append(createTreeNode[getType(obj[key][i])](obj[key], i, indent + 1))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
n.append('<span style="padding-left:'+((indent - 1) * 10)+'px" class="array-type">]</span>')
|
|
||||||
$('div.'+cls).width(largestWidth('div.'+cls))
|
|
||||||
})
|
|
||||||
.appendTo($('<div class="array-type"></div>').appendTo(val))
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
, "object": function (obj, key, indent) {
|
|
||||||
if (!indent) indent = 1;
|
|
||||||
var val = $('<div class="doc-value object"></div>')
|
|
||||||
$('<span class="object-type">{</span><span class="expand" style="float:left">...</span><span class="object-type">}</span>')
|
|
||||||
.click(function (e) {
|
|
||||||
var n = $(this).parent();
|
|
||||||
n.html('')
|
|
||||||
n.append('<span style="padding-left:'+((indent - 1) * 10)+'px" class="object-type">{</span>')
|
|
||||||
for (i in obj[key]) {
|
|
||||||
var field = $('<div class="doc-field"></div>').click(handleTreeClick);
|
|
||||||
var p = $('<div class="id-space" style="margin-left:'+(indent * 10)+'px"/>');
|
|
||||||
var di = $('<div class="object-key">'+i+'</div>')
|
|
||||||
field.append(p)
|
|
||||||
.append(di)
|
|
||||||
.append(createTreeNode[getType(obj[key][i])](obj[key], i, indent + 1))
|
|
||||||
n.append(field)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.append('<span style="padding-left:'+((indent - 1) * 10)+'px" class="object-type">}</span>')
|
|
||||||
di.width(largestWidth('div.object-key'))
|
|
||||||
})
|
|
||||||
.appendTo($('<div class="object-type"></div>').appendTo(val))
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTree(doc) {
|
|
||||||
var d = $('div#document-editor');
|
|
||||||
for (i in doc) {
|
|
||||||
var field = $('<div class="doc-field"></div>').click(handleTreeClick);
|
|
||||||
$('<div class="id-space" />').appendTo(field);
|
|
||||||
field.append('<div class="doc-key doc-key-base">'+i+'</div>')
|
|
||||||
field.append(createTreeNode[getType(doc[i])](doc, i));
|
|
||||||
d.append(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('div.doc-key-base').width(largestWidth('div.doc-key-base'))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inURL: inURL,
|
|
||||||
registerEmitter: registerEmitter,
|
registerEmitter: registerEmitter,
|
||||||
listenFor: listenFor,
|
listenFor: listenFor,
|
||||||
show: show,
|
show: show,
|
||||||
hide: hide,
|
hide: hide,
|
||||||
position: position,
|
position: position,
|
||||||
render: render,
|
render: render,
|
||||||
notify: notify,
|
observeExit: observeExit
|
||||||
observeExit: observeExit,
|
|
||||||
formatMetadata:formatMetadata,
|
|
||||||
getBaseURL:getBaseURL,
|
|
||||||
resetForm: resetForm,
|
|
||||||
delay: delay,
|
|
||||||
persist: persist,
|
|
||||||
lookupPath: lookupPath,
|
|
||||||
selectedTreePath: selectedTreePath,
|
|
||||||
renderTree: renderTree
|
|
||||||
};
|
};
|
||||||
}();
|
}();
|
||||||
|
|||||||
134
src/view.js
134
src/view.js
@ -23,6 +23,47 @@ function parseQueryString(q) {
|
|||||||
return urlParams;
|
return urlParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ## notify
|
||||||
|
//
|
||||||
|
// Create a notification (a div.alert-message in div.alert-messsages) using provide messages and options. Options are:
|
||||||
|
//
|
||||||
|
// * category: warning (default), success, error
|
||||||
|
// * persist: if true alert is persistent, o/w hidden after 3s (default = false)
|
||||||
|
// * loader: if true show loading spinner
|
||||||
|
my.notify = function(message, options) {
|
||||||
|
if (!options) var options = {};
|
||||||
|
var tmplData = _.extend({
|
||||||
|
msg: message,
|
||||||
|
category: 'warning'
|
||||||
|
},
|
||||||
|
options);
|
||||||
|
var _template = ' \
|
||||||
|
<div class="alert-message {{category}} fade in" data-alert="alert"><a class="close" href="#">×</a> \
|
||||||
|
<p>{{msg}} \
|
||||||
|
{{#loader}} \
|
||||||
|
<img src="images/small-spinner.gif" class="notification-loader"> \
|
||||||
|
{{/loader}} \
|
||||||
|
</p> \
|
||||||
|
</div>';
|
||||||
|
var _templated = $.mustache(_template, tmplData);
|
||||||
|
_templated = $(_templated).appendTo($('.data-explorer .alert-messages'));
|
||||||
|
if (!options.persist) {
|
||||||
|
setTimeout(function() {
|
||||||
|
$(_templated).fadeOut(1000, function() {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ## clearNotifications
|
||||||
|
//
|
||||||
|
// Clear all existing notifications
|
||||||
|
my.clearNotifications = function() {
|
||||||
|
var $notifications = $('.data-explorer .alert-message');
|
||||||
|
$notifications.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// The primary view for the entire application.
|
// The primary view for the entire application.
|
||||||
//
|
//
|
||||||
// It should be initialized with a recline.Model.Dataset object and an existing
|
// It should be initialized with a recline.Model.Dataset object and an existing
|
||||||
@ -101,17 +142,36 @@ my.DataExplorer = Backbone.View.extend({
|
|||||||
|
|
||||||
// retrieve basic data like headers etc
|
// retrieve basic data like headers etc
|
||||||
// note this.model and dataset returned are the same
|
// note this.model and dataset returned are the same
|
||||||
this.model.fetch().then(function(dataset) {
|
this.model.fetch()
|
||||||
self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
|
.done(function(dataset) {
|
||||||
// initialize of dataTable calls render
|
self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
|
||||||
self.model.getDocuments(self.config.displayCount);
|
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) {
|
onDisplayCountUpdate: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
|
this.query();
|
||||||
this.model.getDocuments(this.config.displayCount);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setReadOnly: function() {
|
setReadOnly: function() {
|
||||||
@ -180,11 +240,13 @@ my.DataTable = Backbone.View.extend({
|
|||||||
this.model.currentDocuments.bind('reset', this.render);
|
this.model.currentDocuments.bind('reset', this.render);
|
||||||
this.model.currentDocuments.bind('remove', this.render);
|
this.model.currentDocuments.bind('remove', this.render);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
|
this.hiddenHeaders = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click .column-header-menu': 'onColumnHeaderClick'
|
'click .column-header-menu': 'onColumnHeaderClick'
|
||||||
, 'click .row-header-menu': 'onRowHeaderClick'
|
, 'click .row-header-menu': 'onRowHeaderClick'
|
||||||
|
, 'click .root-header-menu': 'onRootHeaderClick'
|
||||||
, 'click .data-table-menu li a': 'onMenuClick'
|
, 'click .data-table-menu li a': 'onMenuClick'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -214,6 +276,11 @@ my.DataTable = Backbone.View.extend({
|
|||||||
util.position('data-table-menu', e);
|
util.position('data-table-menu', e);
|
||||||
util.render('rowActions', 'data-table-menu');
|
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) {
|
onMenuClick: function(e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -221,6 +288,10 @@ my.DataTable = Backbone.View.extend({
|
|||||||
var actions = {
|
var actions = {
|
||||||
bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) },
|
bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) },
|
||||||
transform: function() { self.showTransformDialog('transform') },
|
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 ...
|
// TODO: Delete or re-implement ...
|
||||||
csv: function() { window.location.href = app.csvUrl },
|
csv: function() { window.location.href = app.csvUrl },
|
||||||
json: function() { window.location.href = "_rewrite/api/json" },
|
json: function() { window.location.href = "_rewrite/api/json" },
|
||||||
@ -243,10 +314,10 @@ my.DataTable = Backbone.View.extend({
|
|||||||
});
|
});
|
||||||
doc.destroy().then(function() {
|
doc.destroy().then(function() {
|
||||||
self.model.currentDocuments.remove(doc);
|
self.model.currentDocuments.remove(doc);
|
||||||
util.notify("Row deleted successfully");
|
my.notify("Row deleted successfully");
|
||||||
})
|
})
|
||||||
.fail(function(err) {
|
.fail(function(err) {
|
||||||
util.notify("Errorz! " + err)
|
my.notify("Errorz! " + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +355,20 @@ my.DataTable = Backbone.View.extend({
|
|||||||
$('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
|
$('.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
|
// Core Templating
|
||||||
@ -293,7 +378,14 @@ my.DataTable = Backbone.View.extend({
|
|||||||
<table class="data-table" cellspacing="0"> \
|
<table class="data-table" cellspacing="0"> \
|
||||||
<thead> \
|
<thead> \
|
||||||
<tr> \
|
<tr> \
|
||||||
{{#notEmpty}}<th class="column-header"></th>{{/notEmpty}} \
|
{{#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}} \
|
{{#headers}} \
|
||||||
<th class="column-header"> \
|
<th class="column-header"> \
|
||||||
<div class="column-header-title"> \
|
<div class="column-header-title"> \
|
||||||
@ -311,11 +403,15 @@ my.DataTable = Backbone.View.extend({
|
|||||||
|
|
||||||
toTemplateJSON: function() {
|
toTemplateJSON: function() {
|
||||||
var modelData = this.model.toJSON()
|
var modelData = this.model.toJSON()
|
||||||
modelData.notEmpty = ( modelData.headers.length > 0 )
|
modelData.notEmpty = ( this.headers.length > 0 )
|
||||||
|
modelData.headers = this.headers;
|
||||||
return modelData;
|
return modelData;
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
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());
|
var htmls = $.mustache(this.template, this.toTemplateJSON());
|
||||||
this.el.html(htmls);
|
this.el.html(htmls);
|
||||||
this.model.currentDocuments.forEach(function(doc) {
|
this.model.currentDocuments.forEach(function(doc) {
|
||||||
@ -324,10 +420,11 @@ my.DataTable = Backbone.View.extend({
|
|||||||
var newView = new my.DataTableRow({
|
var newView = new my.DataTableRow({
|
||||||
model: doc,
|
model: doc,
|
||||||
el: tr,
|
el: tr,
|
||||||
headers: self.model.get('headers')
|
headers: self.headers,
|
||||||
});
|
});
|
||||||
newView.render();
|
newView.render();
|
||||||
});
|
});
|
||||||
|
$(".root-header-menu").toggle((self.hiddenHeaders.length > 0));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -343,6 +440,7 @@ my.DataTableRow = Backbone.View.extend({
|
|||||||
this.el = $(this.el);
|
this.el = $(this.el);
|
||||||
this.model.bind('change', this.render);
|
this.model.bind('change', this.render);
|
||||||
},
|
},
|
||||||
|
|
||||||
template: ' \
|
template: ' \
|
||||||
<td><a class="row-header-menu"></a></td> \
|
<td><a class="row-header-menu"></a></td> \
|
||||||
{{#cells}} \
|
{{#cells}} \
|
||||||
@ -398,12 +496,12 @@ my.DataTableRow = Backbone.View.extend({
|
|||||||
var newData = {};
|
var newData = {};
|
||||||
newData[header] = newValue;
|
newData[header] = newValue;
|
||||||
this.model.set(newData);
|
this.model.set(newData);
|
||||||
util.notify("Updating row...", {loader: true});
|
my.notify("Updating row...", {loader: true});
|
||||||
this.model.save().then(function(response) {
|
this.model.save().then(function(response) {
|
||||||
util.notify("Row updated successfully", {category: 'success'});
|
my.notify("Row updated successfully", {category: 'success'});
|
||||||
})
|
})
|
||||||
.fail(function() {
|
.fail(function() {
|
||||||
util.notify('Error saving row', {
|
my.notify('Error saving row', {
|
||||||
category: 'error',
|
category: 'error',
|
||||||
persist: true
|
persist: true
|
||||||
});
|
});
|
||||||
@ -501,11 +599,11 @@ my.ColumnTransform = Backbone.View.extend({
|
|||||||
var funcText = this.el.find('.expression-preview-code').val();
|
var funcText = this.el.find('.expression-preview-code').val();
|
||||||
var editFunc = costco.evalFunction(funcText);
|
var editFunc = costco.evalFunction(funcText);
|
||||||
if (editFunc.errorMessage) {
|
if (editFunc.errorMessage) {
|
||||||
util.notify("Error with function! " + editFunc.errorMessage);
|
my.notify("Error with function! " + editFunc.errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
util.hide('dialog');
|
util.hide('dialog');
|
||||||
util.notify("Updating all visible docs. This could take a while...", {persist: true, loader: true});
|
my.notify("Updating all visible docs. This could take a while...", {persist: true, loader: true});
|
||||||
var docs = self.model.currentDocuments.map(function(doc) {
|
var docs = self.model.currentDocuments.map(function(doc) {
|
||||||
return doc.toJSON();
|
return doc.toJSON();
|
||||||
});
|
});
|
||||||
@ -515,7 +613,7 @@ my.ColumnTransform = Backbone.View.extend({
|
|||||||
function onCompletedUpdate() {
|
function onCompletedUpdate() {
|
||||||
totalToUpdate += -1;
|
totalToUpdate += -1;
|
||||||
if (totalToUpdate === 0) {
|
if (totalToUpdate === 0) {
|
||||||
util.notify(toUpdate.length + " documents updated successfully");
|
my.notify(toUpdate.length + " documents updated successfully");
|
||||||
alert('WARNING: We have only updated the docs in this view. (Updating of all docs not yet implemented!)');
|
alert('WARNING: We have only updated the docs in this view. (Updating of all docs not yet implemented!)');
|
||||||
self.remove();
|
self.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,21 +26,33 @@
|
|||||||
// deep copy so we do not touch original data ...
|
// deep copy so we do not touch original data ...
|
||||||
, data: $.extend(true, {}, indata)
|
, data: $.extend(true, {}, indata)
|
||||||
};
|
};
|
||||||
expect(9);
|
expect(10);
|
||||||
dataset.fetch().then(function(dataset) {
|
dataset.fetch().then(function(dataset) {
|
||||||
equal(dataset.get('name'), metadata.name);
|
equal(dataset.get('name'), metadata.name);
|
||||||
deepEqual(dataset.get('headers'), indata.headers);
|
deepEqual(dataset.get('headers'), indata.headers);
|
||||||
equal(dataset.docCount, 6);
|
equal(dataset.docCount, 6);
|
||||||
dataset.getDocuments(4, 2).then(function(documentList) {
|
var queryObj = {
|
||||||
|
size: 4
|
||||||
|
, offset: 2
|
||||||
|
};
|
||||||
|
dataset.query(queryObj).then(function(documentList) {
|
||||||
deepEqual(indata.rows[2], documentList.models[0].toJSON());
|
deepEqual(indata.rows[2], documentList.models[0].toJSON());
|
||||||
});
|
});
|
||||||
dataset.getDocuments().then(function(docList) {
|
var queryObj = {
|
||||||
// Test getDocuments
|
sort: [
|
||||||
equal(docList.length, Math.min(10, indata.rows.length));
|
['y', 'desc']
|
||||||
|
]
|
||||||
|
};
|
||||||
|
dataset.query(queryObj).then(function(docs) {
|
||||||
|
var doc0 = dataset.currentDocuments.models[0].toJSON();
|
||||||
|
equal(doc0.x, 6);
|
||||||
|
});
|
||||||
|
dataset.query().then(function(docList) {
|
||||||
|
equal(docList.length, Math.min(100, indata.rows.length));
|
||||||
var doc1 = docList.models[0];
|
var doc1 = docList.models[0];
|
||||||
deepEqual(doc1.toJSON(), indata.rows[0]);
|
deepEqual(doc1.toJSON(), indata.rows[0]);
|
||||||
|
|
||||||
// Test UPDATA
|
// Test UPDATE
|
||||||
var newVal = 10;
|
var newVal = 10;
|
||||||
doc1.set({x: newVal});
|
doc1.set({x: newVal});
|
||||||
doc1.save().then(function() {
|
doc1.save().then(function() {
|
||||||
@ -142,26 +154,32 @@
|
|||||||
var stub = sinon.stub($, 'ajax', function(options) {
|
var stub = sinon.stub($, 'ajax', function(options) {
|
||||||
if (options.url.indexOf('schema.json') != -1) {
|
if (options.url.indexOf('schema.json') != -1) {
|
||||||
return {
|
return {
|
||||||
then: function(callback) {
|
done: function(callback) {
|
||||||
callback(webstoreSchema);
|
callback(webstoreSchema);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
fail: function() {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
then: function(callback) {
|
done: function(callback) {
|
||||||
callback(webstoreData);
|
callback(webstoreData);
|
||||||
|
},
|
||||||
|
fail: function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dataset.fetch().then(function(dataset) {
|
dataset.fetch().done(function(dataset) {
|
||||||
deepEqual(['__id__', 'date', 'geometry', 'amount'], dataset.get('headers'));
|
deepEqual(['__id__', 'date', 'geometry', 'amount'], dataset.get('headers'));
|
||||||
equal(3, dataset.docCount)
|
equal(3, dataset.docCount)
|
||||||
dataset.getDocuments().then(function(docList) {
|
// dataset.query().done(function(docList) {
|
||||||
equal(3, docList.length)
|
// equal(3, docList.length)
|
||||||
equal("2009-01-01", docList.models[0].get('date'));
|
// equal("2009-01-01", docList.models[0].get('date'));
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
$.ajax.restore();
|
$.ajax.restore();
|
||||||
});
|
});
|
||||||
@ -243,21 +261,25 @@
|
|||||||
var partialUrl = 'jsonpdataproxy.appspot.com';
|
var partialUrl = 'jsonpdataproxy.appspot.com';
|
||||||
if (options.url.indexOf(partialUrl) != -1) {
|
if (options.url.indexOf(partialUrl) != -1) {
|
||||||
return {
|
return {
|
||||||
then: function(callback) {
|
done: function(callback) {
|
||||||
callback(dataProxyData);
|
callback(dataProxyData);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
fail: function() {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dataset.fetch().then(function(dataset) {
|
dataset.fetch().done(function(dataset) {
|
||||||
deepEqual(['__id__', 'date', 'price'], dataset.get('headers'));
|
deepEqual(['__id__', 'date', 'price'], dataset.get('headers'));
|
||||||
equal(null, dataset.docCount)
|
equal(null, dataset.docCount)
|
||||||
dataset.getDocuments().then(function(docList) {
|
dataset.query().done(function(docList) {
|
||||||
equal(10, docList.length)
|
equal(10, docList.length)
|
||||||
equal("1950-01", docList.models[0].get('date'));
|
equal("1950-01", docList.models[0].get('date'));
|
||||||
// needed only if not stubbing
|
// needed only if not stubbing
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$.ajax.restore();
|
$.ajax.restore();
|
||||||
@ -455,7 +477,7 @@
|
|||||||
console.log('inside dataset:', dataset, dataset.get('headers'), dataset.get('data'));
|
console.log('inside dataset:', dataset, dataset.get('headers'), dataset.get('data'));
|
||||||
deepEqual(['column-2', 'column-1'], dataset.get('headers'));
|
deepEqual(['column-2', 'column-1'], dataset.get('headers'));
|
||||||
//equal(null, dataset.docCount)
|
//equal(null, dataset.docCount)
|
||||||
dataset.getDocuments().then(function(docList) {
|
dataset.query().then(function(docList) {
|
||||||
equal(3, docList.length);
|
equal(3, docList.length);
|
||||||
console.log(docList.models[0]);
|
console.log(docList.models[0]);
|
||||||
equal("A", docList.models[0].get('column-1'));
|
equal("A", docList.models[0].get('column-1'));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user