[all][s]: (refs #1) refactor layout to be like standard js library rather than couch app.
This commit is contained in:
57
src/costco-csv-worker.js
Normal file
57
src/costco-csv-worker.js
Normal file
@@ -0,0 +1,57 @@
|
||||
importScripts('lib/underscore.js');
|
||||
|
||||
onmessage = function(message) {
|
||||
|
||||
function parseCSV(rawCSV) {
|
||||
var patterns = new RegExp((
|
||||
// Delimiters.
|
||||
"(\\,|\\r?\\n|\\r|^)" +
|
||||
// Quoted fields.
|
||||
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
|
||||
// Standard fields.
|
||||
"([^\"\\,\\r\\n]*))"
|
||||
), "gi");
|
||||
|
||||
var rows = [[]], matches = null;
|
||||
|
||||
while (matches = patterns.exec(rawCSV)) {
|
||||
var delimiter = matches[1];
|
||||
|
||||
if (delimiter.length && (delimiter !== ",")) rows.push([]);
|
||||
|
||||
if (matches[2]) {
|
||||
var value = matches[2].replace(new RegExp("\"\"", "g"), "\"");
|
||||
} else {
|
||||
var value = matches[3];
|
||||
}
|
||||
rows[rows.length - 1].push(value);
|
||||
}
|
||||
|
||||
if(_.isEqual(rows[rows.length -1], [""])) rows.pop();
|
||||
|
||||
var docs = [];
|
||||
var headers = _.first(rows);
|
||||
_.each(_.rest(rows), function(row, rowIDX) {
|
||||
var doc = {};
|
||||
_.each(row, function(cell, idx) {
|
||||
doc[headers[idx]] = cell;
|
||||
})
|
||||
docs.push(doc);
|
||||
})
|
||||
|
||||
return docs;
|
||||
}
|
||||
|
||||
var docs = parseCSV(message.data.data);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
req.onprogress = req.upload.onprogress = function(e) {
|
||||
if(e.lengthComputable) postMessage({ percent: (e.loaded / e.total) * 100 });
|
||||
};
|
||||
|
||||
req.onreadystatechange = function() { if (req.readyState == 4) postMessage({done: true, response: req.responseText}) };
|
||||
req.open('POST', message.data.url);
|
||||
req.setRequestHeader('Content-Type', 'application/json');
|
||||
req.send(JSON.stringify({docs: docs}));
|
||||
};
|
||||
169
src/costco.js
Executable file
169
src/costco.js
Executable file
@@ -0,0 +1,169 @@
|
||||
// adapted from https://github.com/harthur/costco. heather rules
|
||||
|
||||
var costco = function() {
|
||||
|
||||
function evalFunction(funcString) {
|
||||
try {
|
||||
eval("var editFunc = " + funcString);
|
||||
} catch(e) {
|
||||
return {errorMessage: e+""};
|
||||
}
|
||||
return editFunc;
|
||||
}
|
||||
|
||||
function previewTransform(docs, editFunc, currentColumn) {
|
||||
var preview = [];
|
||||
var updated = mapDocs($.extend(true, {}, docs), editFunc);
|
||||
for (var i = 0; i < updated.docs.length; i++) {
|
||||
var before = docs[i]
|
||||
, after = updated.docs[i]
|
||||
;
|
||||
if (!after) after = {};
|
||||
if (currentColumn) {
|
||||
preview.push({before: JSON.stringify(before[currentColumn]), after: JSON.stringify(after[currentColumn])});
|
||||
} else {
|
||||
preview.push({before: JSON.stringify(before), after: JSON.stringify(after)});
|
||||
}
|
||||
}
|
||||
util.render('editPreview', 'expression-preview-container', {rows: preview});
|
||||
}
|
||||
|
||||
function mapDocs(docs, editFunc) {
|
||||
var edited = []
|
||||
, deleted = []
|
||||
, failed = []
|
||||
;
|
||||
|
||||
var updatedDocs = _.map(docs, function(doc) {
|
||||
try {
|
||||
var updated = editFunc(_.clone(doc));
|
||||
} catch(e) {
|
||||
failed.push(doc);
|
||||
return;
|
||||
}
|
||||
if(updated === null) {
|
||||
updated = {_deleted: true};
|
||||
edited.push(updated);
|
||||
deleted.push(doc);
|
||||
}
|
||||
else if(updated && !_.isEqual(updated, doc)) {
|
||||
edited.push(updated);
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
|
||||
return {
|
||||
edited: edited,
|
||||
docs: updatedDocs,
|
||||
deleted: deleted,
|
||||
failed: failed
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
evalFunction: evalFunction,
|
||||
previewTransform: previewTransform,
|
||||
mapDocs: mapDocs,
|
||||
updateDocs: updateDocs,
|
||||
updateDoc: updateDoc,
|
||||
uploadDocs: uploadDocs,
|
||||
deleteColumn: deleteColumn,
|
||||
ensureCommit: ensureCommit,
|
||||
uploadCSV: uploadCSV
|
||||
};
|
||||
}();
|
||||
19
src/deps-min.js
vendored
Normal file
19
src/deps-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
234
src/recline.js
Executable file
234
src/recline.js
Executable file
@@ -0,0 +1,234 @@
|
||||
var recline = function() {
|
||||
|
||||
function formatDiskSize(bytes) {
|
||||
return (parseFloat(bytes)/1024/1024).toString().substr(0,4) + "MB"
|
||||
}
|
||||
|
||||
function showDialog(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' });
|
||||
}
|
||||
|
||||
function handleMenuClick() {
|
||||
$( '.menu li' ).click(function(e) {
|
||||
var actions = {
|
||||
bulkEdit: function() { showDialog('bulkEdit', {name: app.currentColumn}) },
|
||||
transform: function() { showDialog('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 '" + app.currentColumn + "' from all documents.";
|
||||
if (confirm(msg)) costco.deleteColumn(app.currentColumn);
|
||||
},
|
||||
deleteRow: function() {
|
||||
var doc = _.find(app.cache, function(doc) { return doc._id === app.currentRow });
|
||||
doc._deleted = true;
|
||||
costco.uploadDocs([doc]).then(
|
||||
function(updatedDocs) {
|
||||
util.notify("Row deleted successfully");
|
||||
recline.initializeTable(app.offset);
|
||||
},
|
||||
function(err) { util.notify("Errorz! " + err) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
util.hide('menu');
|
||||
actions[$(e.target).attr('data-action')]();
|
||||
|
||||
e.preventDefault();
|
||||
})
|
||||
}
|
||||
|
||||
function renderRows(response) {
|
||||
var rows = response.rows;
|
||||
|
||||
if (rows.length < 1) {
|
||||
util.render('dataTable', 'data-table-container');
|
||||
return;
|
||||
};
|
||||
|
||||
var tableRows = [];
|
||||
|
||||
rows.map(function(row) {
|
||||
var cells = [];
|
||||
app.headers.map(function(header) {
|
||||
var value = "";
|
||||
if (row.value[header]) {
|
||||
value = row.value[header];
|
||||
if (typeof(value) == "object") value = JSON.stringify(value);
|
||||
}
|
||||
cells.push({header: header, value: value});
|
||||
})
|
||||
tableRows.push({id: row.value._id, cells: cells});
|
||||
})
|
||||
|
||||
util.render('dataTable', 'data-table-container', {
|
||||
rows: tableRows,
|
||||
headers: app.headers,
|
||||
notEmpty: function() { return app.headers.length > 0 }
|
||||
})
|
||||
|
||||
app.newest = rows[0].id;
|
||||
app.oldest = rows[rows.length - 1].id;
|
||||
app.offset = response.offset;
|
||||
|
||||
function activate(e) {
|
||||
e.removeClass('inaction').addClass('action');
|
||||
}
|
||||
|
||||
function deactivate(e) {
|
||||
e.removeClass('action').addClass('inaction');
|
||||
}
|
||||
|
||||
if (app.offset + getPageSize() >= app.dbInfo.doc_count) {
|
||||
deactivate($( '.viewpanel-paging .last'));
|
||||
deactivate($( '.viewpanel-paging .next'));
|
||||
} else {
|
||||
activate($( '.viewpanel-paging .last'));
|
||||
activate($( '.viewpanel-paging .next'));
|
||||
}
|
||||
|
||||
if (app.offset === 0) {
|
||||
deactivate($( '.viewpanel-paging .previous'));
|
||||
deactivate($( '.viewpanel-paging .first'));
|
||||
} else {
|
||||
activate($( '.viewpanel-paging .previous'));
|
||||
activate($( '.viewpanel-paging .first'));
|
||||
}
|
||||
}
|
||||
|
||||
function activateControls() {
|
||||
$( '.viewPanel-pagingControls-page' ).click(function( e ) {
|
||||
$(".viewpanel-pagesize .selected").removeClass('selected');
|
||||
$(e.target).addClass('selected');
|
||||
fetchRows(app.newest);
|
||||
});
|
||||
$( '.viewpanel-paging a' ).click(function( e ) {
|
||||
var action = $(e.target);
|
||||
if (action.hasClass("last")) fetchRows(false, app.dbInfo.doc_count - getPageSize());
|
||||
if (action.hasClass("next")) fetchRows(app.oldest);
|
||||
if (action.hasClass("previous")) fetchRows(false, app.offset - getPageSize());
|
||||
if (action.hasClass("first")) fetchRows();
|
||||
});
|
||||
}
|
||||
|
||||
function getPageSize() {
|
||||
var pagination = $(".viewpanel-pagesize .selected");
|
||||
if (pagination.length > 0) {
|
||||
return parseInt(pagination.text())
|
||||
} else {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
function fetchRows(id, skip) {
|
||||
|
||||
var query = {
|
||||
"limit" : getPageSize()
|
||||
}
|
||||
|
||||
if (id) {
|
||||
$.extend( query, {"startkey": '"' + id + '"'});
|
||||
if (id !== app.newest) $.extend( query, {"skip": 1});
|
||||
}
|
||||
|
||||
if (skip) $.extend( query, {"skip": skip});
|
||||
|
||||
var req = {url: app.baseURL + 'api/rows?' + $.param(query)};
|
||||
|
||||
couch.request(req).then(function(response) {
|
||||
var offset = response.offset + 1;
|
||||
$('.viewpanel-pagingcount').text(offset + " - " + ((offset - 1) + getPageSize()));
|
||||
app.cache = response.rows.map(function(row) { return row.value; } );
|
||||
renderRows(response);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function updateDocCount(totalDocs) {
|
||||
return couch.request({url: app.baseURL + 'api/_all_docs?' + $.param({startkey: '"_design/"', endkey: '"_design0"'})}).then(
|
||||
function ( data ) {
|
||||
var ddocCount = data.rows.length;
|
||||
$('#docCount').text(totalDocs - ddocCount + " documents");
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getDbInfo(url) {
|
||||
var dfd = $.Deferred();
|
||||
return couch.request({url: url}).then(function(dbInfo) {
|
||||
app.dbInfo = dbInfo;
|
||||
|
||||
$.extend(app.dbInfo, {
|
||||
"host": window.location.host,
|
||||
"disk_size": formatDiskSize(app.dbInfo.disk_size)
|
||||
});
|
||||
|
||||
if( util.inURL("_rewrite", app.baseURL) ) app.dbInfo.db_name = "api";
|
||||
|
||||
dfd.resolve(dbInfo);
|
||||
});
|
||||
return dfd.promise();
|
||||
}
|
||||
|
||||
|
||||
function bootstrap(id) {
|
||||
app.dbPath = app.baseURL + "api/";
|
||||
|
||||
util.listenFor(['esc', 'return']);
|
||||
|
||||
getDbInfo(app.dbPath).then(function( dbInfo ) {
|
||||
util.render('title', 'project-title', dbInfo);
|
||||
util.render( 'generating', 'project-actions' );
|
||||
|
||||
couch.session().then(function(session) {
|
||||
if ( session.userCtx.name ) {
|
||||
var text = "Sign out";
|
||||
} else {
|
||||
var text = "Sign in";
|
||||
}
|
||||
util.render('controls', 'project-controls', {text: text});
|
||||
})
|
||||
|
||||
initializeTable();
|
||||
})
|
||||
}
|
||||
|
||||
function initializeTable(offset) {
|
||||
util.render( 'tableContainer', 'right-panel' );
|
||||
showDialog('busy');
|
||||
couch.request({url: app.dbPath + 'headers'}).then(function ( headers ) {
|
||||
util.hide('dialog');
|
||||
getDbInfo(app.dbPath).then(function(dbInfo) {
|
||||
updateDocCount(dbInfo.doc_count);
|
||||
});
|
||||
app.headers = headers;
|
||||
app.csvUrl = app.dbPath + 'csv?headers=' + escape(JSON.stringify(headers));
|
||||
util.render( 'actions', 'project-actions', $.extend({}, app.dbInfo, {url: app.csvUrl}) );
|
||||
fetchRows(false, offset);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
formatDiskSize: formatDiskSize,
|
||||
handleMenuClick: handleMenuClick,
|
||||
showDialog: showDialog,
|
||||
updateDocCount: updateDocCount,
|
||||
bootstrap: bootstrap,
|
||||
fetchRows: fetchRows,
|
||||
activateControls: activateControls,
|
||||
getPageSize: getPageSize,
|
||||
renderRows: renderRows,
|
||||
initializeTable: initializeTable
|
||||
};
|
||||
}();
|
||||
255
src/site.js
Executable file
255
src/site.js
Executable file
@@ -0,0 +1,255 @@
|
||||
var app = {
|
||||
baseURL: util.getBaseURL(document.location.pathname),
|
||||
container: 'main_content',
|
||||
emitter: util.registerEmitter()
|
||||
};
|
||||
|
||||
app.handler = function(route) {
|
||||
if (route.params && route.params.route) {
|
||||
var path = route.params.route;
|
||||
app.routes[path](route.params.id);
|
||||
} else {
|
||||
app.routes['home']();
|
||||
}
|
||||
};
|
||||
|
||||
app.routes = {
|
||||
home: function() {
|
||||
recline.bootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
app.after = {
|
||||
tableContainer: function() {
|
||||
recline.activateControls();
|
||||
},
|
||||
dataTable: function() {
|
||||
$('.column-header-menu').click(function(e) {
|
||||
app.currentColumn = $(e.target).siblings().text();
|
||||
util.position('menu', e);
|
||||
util.render('columnActions', 'menu');
|
||||
});
|
||||
|
||||
$('.row-header-menu').click(function(e) {
|
||||
app.currentRow = $(e.target).parents('tr:first').attr('data-id');
|
||||
util.position('menu', e);
|
||||
util.render('rowActions', 'menu');
|
||||
});
|
||||
|
||||
$('.data-table-cell-edit').click(function(e) {
|
||||
var editing = $('.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()});
|
||||
})
|
||||
},
|
||||
columnActions: function() { recline.handleMenuClick() },
|
||||
rowActions: function() { recline.handleMenuClick() },
|
||||
cellEditor: function() {
|
||||
$('.data-table-cell-editor .okButton').click(function(e) {
|
||||
var cell = $(e.target);
|
||||
var rowId = cell.parents('tr').attr('data-id');
|
||||
var header = cell.parents('td').attr('data-header');
|
||||
var doc = _.find(app.cache, function(cacheDoc) {
|
||||
return cacheDoc._id === rowId;
|
||||
});
|
||||
doc[header] = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
|
||||
util.notify("Updating row...", {persist: true, loader: true});
|
||||
costco.updateDoc(doc).then(function(response) {
|
||||
util.notify("Row updated successfully");
|
||||
recline.initializeTable();
|
||||
})
|
||||
})
|
||||
$('.data-table-cell-editor .cancelButton').click(function(e) {
|
||||
var cell = $(e.target).parents('.data-table-cell-value');
|
||||
cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
|
||||
})
|
||||
},
|
||||
actions: function() {
|
||||
$('.button').click(function(e) {
|
||||
var action = $(e.target).attr('data-action');
|
||||
util.position('menu', e, {left: -60, top: 5});
|
||||
util.render(action + 'Actions', 'menu');
|
||||
recline.handleMenuClick();
|
||||
});
|
||||
},
|
||||
controls: function() {
|
||||
$('#logged-in-status').click(function(e) {
|
||||
if ($(e.target).text() === "Sign in") {
|
||||
recline.showDialog("signIn");
|
||||
} else if ($(e.target).text() === "Sign out") {
|
||||
util.notify("Signing you out...", {persist: true, loader: true});
|
||||
couch.logout().then(function(response) {
|
||||
util.notify("Signed out");
|
||||
util.render('controls', 'project-controls', {text: "Sign in"});
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
signIn: function() {
|
||||
|
||||
$('.dialog-content #username-input').focus();
|
||||
|
||||
$('.dialog-content').find('#sign-in-form').submit(function(e) {
|
||||
$('.dialog-content .okButton').click();
|
||||
return false;
|
||||
})
|
||||
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
util.hide('dialog');
|
||||
util.notify("Signing you in...", {persist: true, loader: true});
|
||||
var form = $(e.target).parents('.dialog-content').find('#sign-in-form');
|
||||
var credentials = {
|
||||
username: form.find('#username-input').val(),
|
||||
password: form.find('#password-input').val()
|
||||
}
|
||||
couch.login(credentials).then(function(response) {
|
||||
util.notify("Signed in");
|
||||
util.render('controls', 'project-controls', {text: "Sign out"});
|
||||
}, function(error) {
|
||||
if (error.statusText === "error") util.notify(JSON.parse(error.responseText).reason);
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
bulkEdit: function() {
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
var funcText = $('.expression-preview-code').val();
|
||||
var editFunc = costco.evalFunction(funcText);
|
||||
;
|
||||
if (editFunc.errorMessage) {
|
||||
util.notify("Error with function! " + editFunc.errorMessage);
|
||||
return;
|
||||
}
|
||||
util.hide('dialog');
|
||||
costco.updateDocs(editFunc);
|
||||
})
|
||||
|
||||
var editor = $('.expression-preview-code');
|
||||
editor.val("function(doc) {\n doc['"+ app.currentColumn+"'] = doc['"+ app.currentColumn+"'];\n return doc;\n}");
|
||||
editor.focus().get(0).setSelectionRange(18, 18);
|
||||
editor.keydown(function(e) {
|
||||
// if you don't setTimeout it won't grab the latest character if you call e.target.value
|
||||
window.setTimeout( function() {
|
||||
var errors = $('.expression-preview-parsing-status');
|
||||
var editFunc = costco.evalFunction(e.target.value);
|
||||
if (!editFunc.errorMessage) {
|
||||
errors.text('No syntax error.');
|
||||
costco.previewTransform(app.cache, editFunc, app.currentColumn);
|
||||
} else {
|
||||
errors.text(editFunc.errorMessage);
|
||||
}
|
||||
}, 1, true);
|
||||
});
|
||||
editor.keydown();
|
||||
},
|
||||
transform: function() {
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
util.notify("Not implemented yet, sorry! :D");
|
||||
util.hide('dialog');
|
||||
})
|
||||
|
||||
var editor = $('.expression-preview-code');
|
||||
editor.val("function(val) {\n if(_.isString(val)) this.update(\"pizza\")\n}");
|
||||
editor.focus().get(0).setSelectionRange(62,62);
|
||||
editor.keydown(function(e) {
|
||||
// if you don't setTimeout it won't grab the latest character if you call e.target.value
|
||||
window.setTimeout( function() {
|
||||
var errors = $('.expression-preview-parsing-status');
|
||||
var editFunc = costco.evalFunction(e.target.value);
|
||||
if (!editFunc.errorMessage) {
|
||||
errors.text('No syntax error.');
|
||||
var traverseFunc = function(doc) {
|
||||
util.traverse(doc).forEach(editFunc);
|
||||
return doc;
|
||||
}
|
||||
costco.previewTransform(app.cache, traverseFunc);
|
||||
} else {
|
||||
errors.text(editFunc.errorMessage);
|
||||
}
|
||||
}, 1, true);
|
||||
});
|
||||
editor.keydown();
|
||||
},
|
||||
urlImport: function() {
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
app.apiURL = $('#url-input').val().trim();
|
||||
util.notify("Fetching data...", {persist: true, loader: true});
|
||||
$.getJSON(app.apiURL + "?callback=?").then(
|
||||
function(docs) {
|
||||
app.apiDocs = docs;
|
||||
util.notify("Data fetched successfully!");
|
||||
recline.showDialog('jsonTree');
|
||||
},
|
||||
function (err) {
|
||||
util.hide('dialog');
|
||||
util.notify("Data fetch error: " + err.responseText);
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
uploadImport: function() {
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
util.hide('dialog');
|
||||
util.notify("Saving documents...", {persist: true, loader: true});
|
||||
costco.uploadCSV();
|
||||
})
|
||||
},
|
||||
jsonTree: function() {
|
||||
util.renderTree(app.apiDocs);
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
util.hide('dialog');
|
||||
util.notify("Saving documents...", {persist: true, loader: true});
|
||||
costco.uploadDocs(util.lookupPath(util.selectedTreePath())).then(function(msg) {
|
||||
util.notify("Docs saved successfully!");
|
||||
recline.initializeTable(app.offset);
|
||||
});
|
||||
})
|
||||
},
|
||||
pasteImport: function() {
|
||||
$('.dialog-content .okButton').click(function(e) {
|
||||
util.notify("Uploading documents...", {persist: true, loader: true});
|
||||
try {
|
||||
var docs = JSON.parse($('.data-table-cell-copypaste-editor').val());
|
||||
} catch(e) {
|
||||
util.notify("JSON parse error: " + e);
|
||||
}
|
||||
if (docs) {
|
||||
if(_.isArray(docs)) {
|
||||
costco.uploadDocs(docs).then(
|
||||
function(docs) {
|
||||
util.notify("Data uploaded successfully!");
|
||||
recline.initializeTable(app.offset);
|
||||
util.hide('dialog');
|
||||
},
|
||||
function (err) {
|
||||
util.hide('dialog');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
util.notify("Error: JSON must be an array of objects");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.sammy = $.sammy(function () {
|
||||
this.get('', app.handler);
|
||||
this.get("#/", app.handler);
|
||||
this.get("#:route", app.handler);
|
||||
this.get("#:route/:id", app.handler);
|
||||
});
|
||||
|
||||
$(function() {
|
||||
app.emitter.on('error', function(error) {
|
||||
util.notify("Server error: " + error);
|
||||
})
|
||||
|
||||
util.traverse = require('traverse');
|
||||
app.sammy.run();
|
||||
})
|
||||
380
src/util.js
Executable file
380
src/util.js
Executable file
@@ -0,0 +1,380 @@
|
||||
var util = function() {
|
||||
|
||||
$.fn.serializeObject = function() {
|
||||
var o = {};
|
||||
var a = this.serializeArray();
|
||||
$.each(a, function() {
|
||||
if (o[this.name]) {
|
||||
if (!o[this.name].push) {
|
||||
o[this.name] = [o[this.name]];
|
||||
}
|
||||
o[this.name].push(this.value || '');
|
||||
} else {
|
||||
o[this.name] = this.value || '';
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
function inURL(url, str) {
|
||||
var exists = false;
|
||||
if ( url.indexOf( str ) > -1 ) {
|
||||
exists = true;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
function registerEmitter() {
|
||||
var Emitter = function(obj) {
|
||||
this.emit = function(obj, channel) {
|
||||
if (!channel) var channel = 'data';
|
||||
this.trigger(channel, obj);
|
||||
};
|
||||
};
|
||||
MicroEvent.mixin(Emitter);
|
||||
return new Emitter();
|
||||
}
|
||||
|
||||
function listenFor(keys) {
|
||||
var shortcuts = { // from jquery.hotkeys.js
|
||||
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
|
||||
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
|
||||
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
|
||||
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
|
||||
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
|
||||
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
|
||||
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
|
||||
}
|
||||
window.addEventListener("keyup", function(e) {
|
||||
var pressed = shortcuts[e.keyCode];
|
||||
if(_.include(keys, pressed)) app.emitter.emit("keyup", pressed);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function observeExit(elem, callback) {
|
||||
var cancelButton = elem.find('.cancelButton');
|
||||
app.emitter.on('esc', function() {
|
||||
cancelButton.click();
|
||||
app.emitter.clear('esc');
|
||||
});
|
||||
cancelButton.click(callback);
|
||||
}
|
||||
|
||||
function show( thing ) {
|
||||
$('.' + thing ).show();
|
||||
$('.' + thing + '-overlay').show();
|
||||
}
|
||||
|
||||
function hide( thing ) {
|
||||
$('.' + thing ).hide();
|
||||
$('.' + thing + '-overlay').hide();
|
||||
if (thing === "dialog") app.emitter.clear('esc'); // todo more elegant solution
|
||||
}
|
||||
|
||||
function position( thing, elem, offset ) {
|
||||
var position = $(elem.target).offset();
|
||||
if (offset) {
|
||||
if (offset.top) position.top += offset.top;
|
||||
if (offset.left) position.left += offset.left;
|
||||
}
|
||||
$('.' + thing + '-overlay').show().click(function(e) {
|
||||
$(e.target).hide();
|
||||
$('.' + thing).hide();
|
||||
});
|
||||
$('.' + thing).show().css({top: position.top + $(elem.target).height(), left: position.left});
|
||||
}
|
||||
|
||||
function render( template, target, options ) {
|
||||
if ( !options ) options = {data: {}};
|
||||
if ( !options.data ) options = {data: options};
|
||||
var html = $.mustache( $( "." + template + "Template:first" ).html(), options.data );
|
||||
if (target instanceof jQuery) {
|
||||
var targetDom = target;
|
||||
} else {
|
||||
var targetDom = $( "." + target + ":first" );
|
||||
}
|
||||
if( options.append ) {
|
||||
targetDom.append( html );
|
||||
} else {
|
||||
targetDom.html( html );
|
||||
}
|
||||
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 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 {
|
||||
inURL: inURL,
|
||||
registerEmitter: registerEmitter,
|
||||
listenFor: listenFor,
|
||||
show: show,
|
||||
hide: hide,
|
||||
position: position,
|
||||
render: render,
|
||||
notify: notify,
|
||||
observeExit: observeExit,
|
||||
formatMetadata:formatMetadata,
|
||||
getBaseURL:getBaseURL,
|
||||
resetForm: resetForm,
|
||||
delay: delay,
|
||||
persist: persist,
|
||||
lookupPath: lookupPath,
|
||||
selectedTreePath: selectedTreePath,
|
||||
renderTree: renderTree
|
||||
};
|
||||
}();
|
||||
Reference in New Issue
Block a user