Merge branch 'master' into gh-pages

This commit is contained in:
Rufus Pollock 2012-01-11 12:23:48 +00:00
commit 9635bf8745
17 changed files with 3897 additions and 666 deletions

9
css/bootstrap.css vendored Normal file
View File

@ -0,0 +1,9 @@
body {
padding-top: 60px;
}
/* we do not have a LH sidebar */
.container-fluid > .content {
margin-left: 0;
}

514
css/data-explorer.css Normal file
View File

@ -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;
}

50
css/graph-flot.css Normal file
View File

@ -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;
}

BIN
css/images/edit-map.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
css/images/menu-dropdown.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

468
demo/index.html Executable file → Normal file
View File

@ -1,13 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Data Explorer</title>
<link rel="stylesheet" href="style/reset.css" media="screen">
<link rel="stylesheet" href="style/data-table.css" media="screen">
<link rel="stylesheet" href="style/flot-graph.css" media="screen">
<link rel="stylesheet" href="style/style.css" media="screen">
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Recline Data Explorer Demo</title>
<meta name="description" content="A demo of the Recline Data Explorer">
<meta name="author" content="Rufus Pollock and Max Ogden">
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="stylesheet" href="../vendor/bootstrap/1.4.0/bootstrap.css">
<link rel="stylesheet" href="../css/data-explorer.css">
<link rel="stylesheet" href="../css/graph-flot.css">
<link rel="stylesheet" href="../css/bootstrap.css">
<script type="text/javascript" src="../src/deps-min.js"></script>
<script type="text/javascript" src="../vendor/bootstrap/1.4.0/bootstrap-alerts.js"></script>
<script type="text/javascript" src="../src/util.js"></script>
<script type="text/javascript" src="../src/costco.js"></script>
@ -15,437 +24,26 @@
<script type="text/javascript" src="../src/view.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</head>
<body class="bod">
<div class="container">
<div class="menu-overlay" style="display: none; z-index: 101; ">&nbsp;</div>
<ul class="menu">
</ul>
<div id="header">
<div class="project-title">
<a href="http://github.com/okfn/recline">Recline DataExplorer</a>
/ Demo
</div>
<div class="project-actions">
<form class="webstore-load">
<label for="source">Source</label>
<body>
<div class="topbar">
<div class="topbar-inner">
<div class="container-fluid">
<a class="brand" href="#">Recline Data Explorer</a>
<ul class="nav secondary-nav">
<li><a class="set-read-only" title="Put into read-only mode">Read-only</a></li>
</ul>
<form class="webstore-load pull-right" title="Update from the specified webstore dataset">
<input type="text" name="source" size="50" />
<input type="submit" name="" value="Update" />
</form>
</div>
<div class="project-controls"></div>
</div>
<!--
<div class="main_content">
<div class="left-panel"></div>
<div class="right-panel"></div>
</div>
<div class="data-table-container"></div>
-->
</div>
<div id="notification-container">
<div id="notification">
<img src="images/small-spinner.gif" class="notification-loader"><span id="notification-message">Loading...</span>
</div>
</div>
<div class="dialog-overlay" style="display: none; z-index: 101; ">&nbsp;</div>
<div class="dialog ui-draggable" style="display: none; z-index: 102; top: 101px; ">
<div class="dialog-frame" style="width: 700px; visibility: visible; ">
<div class="dialog-content dialog-border"></div>
</div>
</div>
<script type='text/mustache' class="busyTemplate">
<div id="loading-message">
<img src="images/large-spinner.gif">
<span> Working...</span>
</div>
</script>
<script type='text/mustache' class="controlsTemplate">
<a id="logged-in-status" href="JavaScript:void(0);" class="secondary">{{text}}</a>
</script>
<script type='text/mustache' class="actionsTemplate">
<a class="button" data-action="import" href="javascript:{}"><span data-action="import" class="button-menu">Import</span></a>
<!-- <a class="button" data-action="edit" href="javascript:{}"><span data-action="transform" class="button-menu">Edit</span></a> -->
<a class="button" data-action="export" href="javascript:{}"><span data-action="export" class="button-menu">Export</span></a>
</script>
<script type='text/mustache' class="importActionsTemplate">
<li><a data-action="urlImport" class="menuAction" href="JavaScript:void(0);">JSON API</a></li>
<li><a data-action="pasteImport" class="menuAction" href="JavaScript:void(0);">Paste JSON</a></li>
<li><a data-action="uploadImport" class="menuAction" href="JavaScript:void(0);">Upload CSV</a></li>
</script>
<script type='text/mustache' class="exportActionsTemplate">
<li><a data-action="csv" class="menuAction" href="JavaScript:void(0);">CSV</a></li>
<li><a data-action="json" class="menuAction" href="JavaScript:void(0);">JSON</a></li>
</script>
<script type='text/mustache' class="transformActionsTemplate">
<li><a data-action="transform" class="menuAction" href="JavaScript:void(0);">Global transform...</a></li>
</script>
<script type='text/mustache' class="columnActionsTemplate">
<li><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>
</script>
<script type='text/mustache' class="rowActionsTemplate">
<li><a data-action="deleteRow" class="menuAction" href="JavaScript:void(0);">Delete this row</a></li>
</script>
<script type='text/mustache' class="titleTemplate"><span id="project-name-button" class="app-path-section">{{db_name}}</span></script>
<script type='text/mustache' class="bulkTemplate">http://{{host}}/{{db_name}}/_bulk_docs</script>
<script type='text/mustache' class="generatingTemplate"><div class="loading">Loading...</div></script>
<script type='text/mustache' class="tableContainerTemplate">
<div id="tool-panel">
<div id="summary-bar">
<span id="docCount"></span>
</div>
<div id="download">
</div>
</div>
<div id="view-panel">
<div class="viewpanel-header">
<div class="viewpanel-pagesize">
<span>
Show:
</span>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">5</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page selected">10</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">25</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">50</a>
<span>
rows
</span>
</div>
<div class="viewpanel-sorting">
</div>
<div class="viewpanel-paging">
<a href="javascript:{}" class="first inaction">« first</a>
<a href="javascript:{}" class="previous inaction"> previous</a>
<span class="viewpanel-pagingcount">
1 - 10
</span>
<a href="javascript:{}" class="next action">next </a>
<a href="javascript:{}" class="last action">last »</a>
</div>
</div>
<div class="data-table-container">
</div>
</div>
</script>
<script type='text/mustache' class="dataTableTemplate">
<table class="data-table" cellspacing="0">
<tbody>
<tr>
{{#notEmpty}}<td class="column-header"></td>{{/notEmpty}}
{{#headers}}
<td class="column-header">
<div class="column-header-title">
<a class="column-header-menu"></a>
<span class="column-header-name">{{.}}</span>
</div>
</div>
</td>
{{/headers}}
</tr>
{{#rows}}
<tr data-id="{{id}}">
<td><a class="row-header-menu"></a></td>
{{#cells}}
<td data-header="{{header}}">
<div class="data-table-cell-content">
<a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a>
<div class="data-table-cell-value">{{value}}</div>
</div>
</td>
{{/cells}}
</tr>
{{/rows}}
</tbody>
</table>
</script>
<script type='text/mustache' class="signInTemplate">
<div class="dialog-header">
Sign in
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<form name="sign-in-form" id="sign-in-form">
<table class="form-table">
<tbody>
<tr>
<th>
<label for="username">Username</label>
</th>
<td>
<input type="text" size="25" id="username-input" name="username">
</td>
</tr>
<tr>
<th>
<label for="password">Password</label>
</th>
<td>
<input type="password" size="25" id="password-input" name="password">
</td>
</tr>
<input type="submit" style="height: 0px; width: 0px; border: none; padding: 0px;" hidefocus="true" />
</tbody>
</table>
</form>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Sign in&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="transformTemplate">
<div class="dialog-header">
Recursive transform on all rows
</div>
<div class="dialog-body">
<div class="grid-layout layout-full">
<p class="info">Traverse and transform objects by visiting every node on a recursive walk using <a href="https://github.com/substack/js-traverse">js-traverse</a>.</p>
<table>
<tbody>
<tr>
<td colspan="4">
<div class="grid-layout layout-tight layout-full">
<table rows="4" cols="4">
<tbody>
<tr style="vertical-align: bottom;">
<td colspan="4">
Expression
</td>
</tr>
<tr>
<td colspan="3">
<div class="input-container">
<textarea class="expression-preview-code"></textarea>
</div>
</td>
<td class="expression-preview-parsing-status" width="150" style="vertical-align: top;">
No syntax error.
</td>
</tr>
<tr>
<td colspan="4">
<div id="expression-preview-tabs" class="refine-tabs ui-tabs ui-widget ui-widget-content ui-corner-all">
<span>Preview</span>
<div id="expression-preview-tabs-preview" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
<div class="expression-preview-container" style="width: 652px; ">
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Update All&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
</div>
<script type='text/mustache' class="urlImportTemplate">
<div class="dialog-header">
Download and import from a URL or API
<div class="container-fluid">
<div class="content">
<div class="data-explorer-here"></div>
</div>
<div class="dialog-body">
<div class="grid-layout layout-full">
<p class="info">
Currently only <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>-enabled APIs are supported, for example:
</p>
<p class="info">
<code>https://api.github.com/repos/maxogden/recline/commits</code>
</p>
<form name="api-import-form" id="sign-in-form">
<table class="form-table">
<tbody>
<tr>
<th>
<label for="url">URL</label>
</th>
<td>
<input type="text" size="65" id="url-input" name="url">
</td>
</tr>
<input type="submit" style="height: 0px; width: 0px; border: none; padding: 0px; display: none;" hidefocus="true" />
</tbody>
</table>
</form>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Fetch&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="pasteImportTemplate">
<div class="dialog-header">
Import raw copy & pasted JSON
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<p class="info">
Paste in an array of JSON objects representing the documents that you would like to insert into the database.
</p>
<p class="info">
<code>[{"woo": "pizza"}, {"tasty": "muffins"}]</code>
</p>
<div class="menu-container data-table-cell-editor">
<textarea class="data-table-cell-copypaste-editor" bind="textarea">{{value}}</textarea>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="uploadImportTemplate">
<div class="dialog-header">
Upload and import a CSV
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<strong>Please choose a CSV file to upload:</strong><br />
<input type="file" id="file" />
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="bulkEditTemplate">
<div class="dialog-header">
Functional transform on column {{name}}
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<table>
<tbody>
<tr>
<td colspan="4">
<div class="grid-layout layout-tight layout-full">
<table rows="4" cols="4">
<tbody>
<tr style="vertical-align: bottom;">
<td colspan="4">
Expression
</td>
</tr>
<tr>
<td colspan="3">
<div class="input-container">
<textarea class="expression-preview-code"></textarea>
</div>
</td>
<td class="expression-preview-parsing-status" width="150" style="vertical-align: top;">
No syntax error.
</td>
</tr>
<tr>
<td colspan="4">
<div id="expression-preview-tabs" class="refine-tabs ui-tabs ui-widget ui-widget-content ui-corner-all">
<span>Preview</span>
<div id="expression-preview-tabs-preview" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
<div class="expression-preview-container" style="width: 652px; ">
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Update All&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="cellEditorTemplate">
<div class="menu-container data-table-cell-editor">
<textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea>
<div id="data-table-cell-editor-actions">
<div class="data-table-cell-editor-action">
<button class="okButton button">Update</button>
</div>
<div class="data-table-cell-editor-action">
<button class="cancelButton button">Cancel</button>
</div>
</div>
</div>
</script>
<script type='text/mustache' class="jsonTreeTemplate">
<div class="dialog-header">
Please highlight the array of JSON objects to convert to documents.
</div>
<div class="dialog-body">
<div id="document-container">
<div id="document-editor"></div>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="editPreviewTemplate">
<div class="expression-preview-table-wrapper">
<table>
<tbody>
<tr>
<td class="expression-preview-heading">
before
</td>
<td class="expression-preview-heading">
after
</td>
</tr>
{{#rows}}
<tr>
<td class="expression-preview-value">
{{before}}
</td>
<td class="expression-preview-value">
{{after}}
</td>
</tr>
{{/rows}}
</tbody>
</table>
</div>
</script>
</div>
</body>
</html>

View File

@ -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() {

241
demo/original.html Executable file
View File

@ -0,0 +1,241 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Data Explorer</title>
<link rel="stylesheet" href="style/reset.css" media="screen">
<link rel="stylesheet" href="style/data-table.css" media="screen">
<link rel="stylesheet" href="style/flot-graph.css" media="screen">
<link rel="stylesheet" href="style/style.css" media="screen">
<script type="text/javascript" src="../src/deps-min.js"></script>
<script type="text/javascript" src="../src/util.js"></script>
<script type="text/javascript" src="../src/costco.js"></script>
<script type="text/javascript" src="../src/model.js"></script>
<script type="text/javascript" src="../src/view.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</head>
<body class="bod">
<div class="container">
<div id="header">
<div class="project-title">
<a href="http://github.com/okfn/recline">Recline DataExplorer</a>
/ Demo
</div>
<div class="project-actions">
<form class="webstore-load">
<label for="source">Source</label>
<input type="text" name="source" size="50" />
<input type="submit" name="" value="Update" />
</form>
</div>
<div class="project-controls"></div>
</div>
<div class="data-explorer-here"></div>
<!--
<div class="main_content">
<div class="left-panel"></div>
<div class="right-panel"></div>
</div>
<div class="data-table-container"></div>
-->
</div>
<script type='text/mustache' class="busyTemplate">
<div id="loading-message">
<img src="images/large-spinner.gif">
<span> Working...</span>
</div>
</script>
<script type='text/mustache' class="controlsTemplate">
<a id="logged-in-status" href="JavaScript:void(0);" class="secondary">{{text}}</a>
</script>
<script type='text/mustache' class="actionsTemplate">
<a class="button" data-action="import" href="javascript:{}"><span data-action="import" class="button-menu">Import</span></a>
<!-- <a class="button" data-action="edit" href="javascript:{}"><span data-action="transform" class="button-menu">Edit</span></a> -->
<a class="button" data-action="export" href="javascript:{}"><span data-action="export" class="button-menu">Export</span></a>
</script>
<script type='text/mustache' class="importActionsTemplate">
<li><a data-action="urlImport" class="menuAction" href="JavaScript:void(0);">JSON API</a></li>
<li><a data-action="pasteImport" class="menuAction" href="JavaScript:void(0);">Paste JSON</a></li>
<li><a data-action="uploadImport" class="menuAction" href="JavaScript:void(0);">Upload CSV</a></li>
</script>
<script type='text/mustache' class="exportActionsTemplate">
<li><a data-action="csv" class="menuAction" href="JavaScript:void(0);">CSV</a></li>
<li><a data-action="json" class="menuAction" href="JavaScript:void(0);">JSON</a></li>
</script>
<script type='text/mustache' class="titleTemplate"><span id="project-name-button" class="app-path-section">{{db_name}}</span></script>
<script type='text/mustache' class="bulkTemplate">http://{{host}}/{{db_name}}/_bulk_docs</script>
<script type='text/mustache' class="generatingTemplate"><div class="loading">Loading...</div></script>
<script type='text/mustache' class="tableContainerTemplate">
<div id="tool-panel">
<div id="summary-bar">
<span id="docCount"></span>
</div>
<div id="download">
</div>
</div>
<div id="view-panel">
<div class="viewpanel-header">
<div class="viewpanel-pagesize">
<span>
Show:
</span>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">5</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page selected">10</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">25</a>
<a href="javascript:{}" class="viewPanel-pagingControls-page action">50</a>
<span>
rows
</span>
</div>
<div class="viewpanel-sorting">
</div>
<div class="viewpanel-paging">
<a href="javascript:{}" class="first inaction">« first</a>
<a href="javascript:{}" class="previous inaction"> previous</a>
<span class="viewpanel-pagingcount">
1 - 10
</span>
<a href="javascript:{}" class="next action">next </a>
<a href="javascript:{}" class="last action">last »</a>
</div>
</div>
<div class="data-table-container">
</div>
</div>
</script>
<script type='text/mustache' class="signInTemplate">
<div class="dialog-header">
Sign in
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<form name="sign-in-form" id="sign-in-form">
<table class="form-table">
<tbody>
<tr>
<th>
<label for="username">Username</label>
</th>
<td>
<input type="text" size="25" id="username-input" name="username">
</td>
</tr>
<tr>
<th>
<label for="password">Password</label>
</th>
<td>
<input type="password" size="25" id="password-input" name="password">
</td>
</tr>
<input type="submit" style="height: 0px; width: 0px; border: none; padding: 0px;" hidefocus="true" />
</tbody>
</table>
</form>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Sign in&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="urlImportTemplate">
<div class="dialog-header">
Download and import from a URL or API
</div>
<div class="dialog-body">
<div class="grid-layout layout-full">
<p class="info">
Currently only <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>-enabled APIs are supported, for example:
</p>
<p class="info">
<code>https://api.github.com/repos/maxogden/recline/commits</code>
</p>
<form name="api-import-form" id="sign-in-form">
<table class="form-table">
<tbody>
<tr>
<th>
<label for="url">URL</label>
</th>
<td>
<input type="text" size="65" id="url-input" name="url">
</td>
</tr>
<input type="submit" style="height: 0px; width: 0px; border: none; padding: 0px; display: none;" hidefocus="true" />
</tbody>
</table>
</form>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Fetch&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="pasteImportTemplate">
<div class="dialog-header">
Import raw copy & pasted JSON
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<p class="info">
Paste in an array of JSON objects representing the documents that you would like to insert into the database.
</p>
<p class="info">
<code>[{"woo": "pizza"}, {"tasty": "muffins"}]</code>
</p>
<div class="menu-container data-table-cell-editor">
<textarea class="data-table-cell-copypaste-editor" bind="textarea">{{value}}</textarea>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="uploadImportTemplate">
<div class="dialog-header">
Upload and import a CSV
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<strong>Please choose a CSV file to upload:</strong><br />
<input type="file" id="file" />
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="jsonTreeTemplate">
<div class="dialog-header">
Please highlight the array of JSON objects to convert to documents.
</div>
<div class="dialog-body">
<div id="document-container">
<div id="document-editor"></div>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
</body>
</html>

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -1,4 +1,51 @@
var util = function() {
var templates = {
transformActions: '<li><a data-action="transform" class="menuAction" href="JavaScript:void(0);">Global transform...</a></li>'
, columnActions: ' \
<li><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> \
'
, rowActions: '<li><a data-action="deleteRow" class="menuAction" href="JavaScript:void(0);">Delete this row</a></li>'
, cellEditor: ' \
<div class="menu-container data-table-cell-editor"> \
<textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
<div id="data-table-cell-editor-actions"> \
<div class="data-table-cell-editor-action"> \
<button class="okButton btn primary">Update</button> \
<button class="cancelButton btn danger">Cancel</button> \
</div> \
</div> \
</div> \
'
, editPreview: ' \
<div class="expression-preview-table-wrapper"> \
<table> \
<thead> \
<tr> \
<th class="expression-preview-heading"> \
before \
</th> \
<th class="expression-preview-heading"> \
after \
</th> \
</tr> \
</thead> \
<tbody> \
{{#rows}} \
<tr> \
<td class="expression-preview-value"> \
{{before}} \
</td> \
<td class="expression-preview-value"> \
{{after}} \
</td> \
</tr> \
{{/rows}} \
</tbody> \
</table> \
</div> \
'
};
$.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 = ' \
<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) {

View File

@ -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: ' \
<div class="nav"> \
<span class="nav-toggle"> \
<input type="radio" id="datatable" name="nav-toggle" value="datatable" checked="checked" /> \
<label for="nav-datatable">Data Table</label> \
<input type="radio" id="nav-graph" name="nav-toggle" value="graph" /> \
<label for="nav-graph">Graph</label> \
</span> \
<ul class="nav-pagination"> \
<li><form class="display-count"><label for="per-page">Display count</label> <input name="displayCount" type="text" value="{{displayCount}}" /></form></li> \
<div class="data-explorer"> \
<div class="alert-messages"></div> \
\
<div class="header"> \
<ul class="navigation"> \
<li class="active"><a href="#grid" class="btn">Grid</a> \
<li><a href="#graph" class="btn">Graph</a></li> \
</ul> \
<div class="pagination"> \
<form class="display-count"> \
Showing 0 to <input name="displayCount" type="text" value="{{displayCount}}" /> of <span class="doc-count">{{docCount}}</span> \
</form> \
</div> \
</div> \
<div class="data-view-container"></div> \
<div class="dialog-overlay" style="display: none; z-index: 101; ">&nbsp;</div> \
<div class="dialog ui-draggable" style="display: none; z-index: 102; top: 101px; "> \
<div class="dialog-frame" style="width: 700px; visibility: visible; "> \
<div class="dialog-content dialog-border"></div> \
</div> \
</div> \
</div> \
',
events: {
'change input[name="nav-toggle"]': 'navChange',
'submit form.display-count': 'displayCountUpdate'
'submit form.display-count': 'onDisplayCountUpdate'
},
initialize: function(options) {
var self = this;
this.el = $(this.el);
this.config = options.config || {};
_.extend(this.config, {
displayCount: 10
, readOnly: false
});
this.draw();
},
displayCountUpdate: function(e) {
e.preventDefault();
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
this.draw();
},
draw: function() {
var self = this;
this.el.empty();
if (this.config.readOnly) {
this.setReadOnly();
}
// Hash of 'page' views (i.e. those for whole page) keyed by page name
this.pageViews = {
grid: new my.DataTable({
model: this.model
})
, graph: new my.FlotGraph({
model: this.model
})
};
// this must be called after pageViews are created
this.render();
this.$dataViewContainer = this.el.find('.data-view-container');
this.router = new Backbone.Router();
this.setupRouting();
Backbone.history.start();
// retrieve basic data like headers etc
// note this.model and dataset returned are the same
this.model.fetch().then(function(dataset) {
self.el.find('.doc-count').text(self.model.docCount);
// initialize of dataTable calls render
self.dataTable = new my.DataTable({
model: dataset
});
self.flotGraph = new my.FlotGraph({
model: dataset
});
self.flotGraph.el.hide();
self.$dataViewContainer.append(self.dataTable.el)
self.$dataViewContainer.append(self.flotGraph.el);
self.model.getRows(self.config.displayCount);
self.model.getDocuments(self.config.displayCount);
});
},
render: function() {
var template = $.mustache(this.template, this.config);
$(this.el).html(template);
onDisplayCountUpdate: function(e) {
e.preventDefault();
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
this.model.getDocuments(this.config.displayCount);
},
navChange: function(e) {
// TODO: really ugly and will not scale to more widgets ...
var widgetToShow = $(e.target).val();
if (widgetToShow == 'datatable') {
this.flotGraph.el.hide();
this.dataTable.el.show();
} else if (widgetToShow == 'graph') {
this.flotGraph.el.show();
this.dataTable.el.hide();
// Have to call this here
// If you attempt to render with flot when graph is hidden / invisible flot will complain with
// Invalid dimensions for plot, width = 0, height = 0
// (Could hack this by moving plot left -1000 or similar ...)
this.flotGraph.createPlot();
}
setReadOnly: function() {
this.el.addClass('read-only');
},
render: function() {
var tmplData = this.model.toTemplateJSON();
tmplData.displayCount = this.config.displayCount;
var template = $.mustache(this.template, tmplData);
$(this.el).html(template);
var $dataViewContainer = this.el.find('.data-view-container');
_.each(this.pageViews, function(view, pageName) {
$dataViewContainer.append(view.el)
});
},
setupRouting: function() {
var self = this;
this.router.route('', 'grid', function() {
self.updateNav('grid');
});
this.router.route(/grid(\?.*)?/, 'view', function(queryString) {
self.updateNav('grid', queryString);
});
this.router.route(/graph(\?.*)?/, 'graph', function(queryString) {
self.updateNav('graph', queryString);
// we have to call here due to fact plot may not have been able to draw
// if it was hidden until now - see comments in FlotGraph.redraw
qsParsed = parseQueryString(queryString);
if ('graph' in qsParsed) {
var chartConfig = JSON.parse(qsParsed['graph']);
_.extend(self.pageViews['graph'].chartConfig, chartConfig);
}
self.pageViews['graph'].redraw();
});
},
updateNav: function(pageName, queryString) {
this.el.find('.navigation li').removeClass('active');
var $el = this.el.find('.navigation li a[href=#' + pageName + ']');
$el.parent().addClass('active');
// show the specific page
_.each(this.pageViews, function(view, pageViewName) {
if (pageViewName === pageName) {
view.el.show();
} else {
view.el.hide();
}
});
}
});
@ -103,29 +181,24 @@ my.DataTable = Backbone.View.extend({
this.model.currentDocuments.bind('reset', this.render);
this.model.currentDocuments.bind('remove', this.render);
this.state = {};
// this is nasty. Due to fact that .menu element is not inside this view but is elsewhere in DOM
$('.menu li a').live('click', function(e) {
// self.onMenuClick(e).apply(self);
self.onMenuClick(e);
});
},
events: {
// see initialize
// 'click .menu li': 'onMenuClick',
'click .column-header-menu': 'onColumnHeaderClick',
'click .row-header-menu': 'onRowHeaderClick'
'click .column-header-menu': 'onColumnHeaderClick'
, 'click .row-header-menu': 'onRowHeaderClick'
, 'click .data-table-menu li a': 'onMenuClick'
},
showDialog: function(template, data) {
if (!data) data = {};
util.show('dialog');
util.render(template, 'dialog-content', data);
util.observeExit($('.dialog-content'), function() {
util.hide('dialog');
})
$('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
},
// TODO: delete or re-enable (currently this code is not used from anywhere except deprecated or disabled methods (see above)).
// showDialog: function(template, data) {
// if (!data) data = {};
// util.show('dialog');
// util.render(template, 'dialog-content', data);
// util.observeExit($('.dialog-content'), function() {
// util.hide('dialog');
// })
// $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
// },
// ======================================================
@ -133,14 +206,14 @@ my.DataTable = Backbone.View.extend({
onColumnHeaderClick: function(e) {
this.state.currentColumn = $(e.target).siblings().text();
util.position('menu', e);
util.render('columnActions', 'menu');
util.position('data-table-menu', e);
util.render('columnActions', 'data-table-menu');
},
onRowHeaderClick: function(e) {
this.state.currentRow = $(e.target).parents('tr:first').attr('data-id');
util.position('menu', e);
util.render('rowActions', 'menu');
util.position('data-table-menu', e);
util.render('rowActions', 'data-table-menu');
},
onMenuClick: function(e) {
@ -149,11 +222,13 @@ my.DataTable = Backbone.View.extend({
var actions = {
bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) },
transform: function() { self.showTransformDialog('transform') },
// TODO: Delete or re-implement ...
csv: function() { window.location.href = app.csvUrl },
json: function() { window.location.href = "_rewrite/api/json" },
urlImport: function() { showDialog('urlImport') },
pasteImport: function() { showDialog('pasteImport') },
uploadImport: function() { showDialog('uploadImport') },
// END TODO
deleteColumn: function() {
var msg = "Are you sure? This will delete '" + self.state.currentColumn + "' from all documents.";
// TODO:
@ -176,7 +251,7 @@ my.DataTable = Backbone.View.extend({
})
}
}
util.hide('menu');
util.hide('data-table-menu');
actions[$(e.target).attr('data-action')]();
},
@ -214,18 +289,20 @@ my.DataTable = Backbone.View.extend({
// ======================================================
// Core Templating
template: ' \
<div class="data-table-menu-overlay" style="display: none; z-index: 101; ">&nbsp;</div> \
<ul class="data-table-menu"></ul> \
<table class="data-table" cellspacing="0"> \
<thead> \
<tr> \
{{#notEmpty}}<td class="column-header"></td>{{/notEmpty}} \
{{#notEmpty}}<th class="column-header"></th>{{/notEmpty}} \
{{#headers}} \
<td class="column-header"> \
<th class="column-header"> \
<div class="column-header-title"> \
<a class="column-header-menu"></a> \
<span class="column-header-name">{{.}}</span> \
</div> \
</div> \
</td> \
</th> \
{{/headers}} \
</tr> \
</thead> \
@ -240,9 +317,7 @@ my.DataTable = Backbone.View.extend({
},
render: function() {
var self = this;
var template = $( ".dataTableTemplate:first" ).html()
, htmls = $.mustache(template, this.toTemplateJSON())
;
var htmls = $.mustache(this.template, this.toTemplateJSON());
this.el.html(htmls);
this.model.currentDocuments.forEach(function(doc) {
var tr = $('<tr />');
@ -324,12 +399,15 @@ my.DataTableRow = Backbone.View.extend({
var newData = {};
newData[header] = newValue;
this.model.set(newData);
util.notify("Updating row...", {persist: true, loader: true});
util.notify("Updating row...", {loader: true});
this.model.save().then(function(response) {
util.notify("Row updated successfully");
util.notify("Row updated successfully", {category: 'success'});
})
.fail(function() {
alert('error saving');
util.notify('Error saving row', {
category: 'error',
persist: true
});
});
},
@ -392,8 +470,8 @@ my.ColumnTransform = Backbone.View.extend({
</div> \
</div> \
<div class="dialog-footer"> \
<button class="okButton button">&nbsp;&nbsp;Update All&nbsp;&nbsp;</button> \
<button class="cancelButton button">Cancel</button> \
<button class="okButton btn primary">&nbsp;&nbsp;Update All&nbsp;&nbsp;</button> \
<button class="cancelButton btn danger">Cancel</button> \
</div> \
',
@ -462,7 +540,8 @@ my.ColumnTransform = Backbone.View.extend({
var docs = self.model.currentDocuments.map(function(doc) {
return doc.toJSON();
});
costco.previewTransform(docs, editFunc, self.state.currentColumn);
var previewData = costco.previewTransform(docs, editFunc, self.state.currentColumn);
util.render('editPreview', 'expression-preview-container', {rows: previewData});
} else {
errors.text(editFunc.errorMessage);
}
@ -538,93 +617,99 @@ my.DataTransform = Backbone.View.extend({
});
// Graph view for a Dataset using Flot graphing library.
//
// Initialization arguments:
//
// * model: recline.Model.Dataset
// * config: (optional) graph configuration hash of form:
//
// {
// group: {column name for x-axis},
// series: [{column name for series A}, {column name series B}, ... ],
// graphType: 'line'
// }
//
// NB: should *not* provide an el argument to the view but must let the view
// generate the element itself (you can then append view.el to the DOM.
my.FlotGraph = Backbone.View.extend({
tagName: "div",
className: "data-graph-container",
// TODO: normalize css
template: ' \
<div class="panel graph"></div> \
<div class="editor"> \
<div class="editor-info editor-hide-info"> \
<h1><span></span>Help</h1> \
<h3 class="action-toggle-help">Help &raquo;</h3> \
<p>To create a chart select a column (group) to use as the x-axis \
then another column (Series A) to plot against it.</p> \
<p>You can add add \
additional series by clicking the "Add series" button</p> \
<p>Please note you must be logged in to save charts.</p> \
</div> \
<form> \
<ul> \
<li class="editor-type"> \
<label>Graph Type</label> \
<form class="form-stacked"> \
<div class="clearfix"> \
<label>Graph Type</label> \
<div class="input editor-type"> \
<select> \
<option value="line">Line</option> \
</select> \
</li> \
<li class="editor-group"> \
<label>Group Column (x-axis)</label> \
</div> \
<label>Group Column (x-axis)</label> \
<div class="input editor-group"> \
<select> \
{{#headers}} \
<option value="{{.}}">{{.}}</option> \
{{/headers}} \
</select> \
</li> \
<li class="editor-series"> \
<label>Series <span>A (y-axis)</span></label> \
<select> \
{{#headers}} \
<option value="{{.}}">{{.}}</option> \
{{/headers}} \
</select> \
</li> \
</ul> \
<div class="editor-buttons"> \
<button class="editor-add">Add Series</button> \
</div> \
<div class="editor-series-group"> \
<div class="editor-series"> \
<label>Series <span>A (y-axis)</span></label> \
<div class="input"> \
<select> \
{{#headers}} \
<option value="{{.}}">{{.}}</option> \
{{/headers}} \
</select> \
</div> \
</div> \
</div> \
</div> \
<div class="editor-buttons editor-submit"> \
<div class="editor-buttons"> \
<button class="btn editor-add">Add Series</button> \
</div> \
<div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
<button class="editor-save">Save</button> \
<input type="hidden" class="editor-id" value="chart-1" /> \
</div> \
</form> \
</div> \
<div class="panel graph"></div> \
</div> \
',
initialize: function(options, chart) {
events: {
'change form select': 'onEditorSubmit'
, 'click .editor-add': 'addSeries'
, 'click .action-remove-series': 'removeSeries'
, 'click .action-toggle-help': 'toggleHelp'
},
initialize: function(options, config) {
var self = this;
this.el = $(this.el);
_.bindAll(this, 'render');
this.model.currentDocuments.bind('add', this.render);
this.model.currentDocuments.bind('reset', this.render);
this.chart = chart;
this.chartConfig = {
group: null,
series: [],
graphType: 'line'
};
},
events: {
// 'change select': 'onEditorSubmit'
},
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
this._getEditorData();
this.plot.setData(this.createSeries());
this.plot.resize();
this.plot.setupGrid();
this.plot.draw();
},
_getEditorData: function() {
$editor = this
var series = this.$series.map(function () {
return $(this).val();
});
this.chartConfig.series = $.makeArray(series)
this.chartConfig.group = this.el.find('.editor-group select').val();
_.bindAll(this, 'render', 'redraw');
// we need the model.headers to render properly
this.model.bind('change', this.render);
this.model.currentDocuments.bind('add', this.redraw);
this.model.currentDocuments.bind('reset', this.redraw);
this.chartConfig = _.extend({
group: null,
series: [],
graphType: 'line'
},
config)
this.render();
},
toTemplateJSON: function() {
@ -636,24 +721,56 @@ my.FlotGraph = Backbone.View.extend({
$(this.el).html(htmls);
// now set a load of stuff up
this.$graph = this.el.find('.panel.graph');
// event approach did not seem to work
this.$series = this.el.find('.editor-series select');
this.$seriesClone = this.$series.parent().clone();
var self = this;
this.el.find('form select').change(function() {
self.onEditorSubmit.apply(self, arguments)
});
// for use later when adding additional series
// could be simpler just to have a common template!
this.$seriesClone = this.el.find('.editor-series').clone();
this._updateSeries();
return this;
},
createPlot: function () {
// only lines for the present
options = {
id: 'line',
name: 'Line Chart'
};
this.plot = $.plot(this.$graph, this.createSeries(), options);
return this;
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
this._getEditorData();
// update navigation
// TODO: make this less invasive (e.g. preserve other keys in query string)
window.location.hash = window.location.hash.split('?')[0] +
'?graph=' + JSON.stringify(this.chartConfig);
this.redraw();
},
redraw: function() {
// There appear to be issues generating a Flot graph if either:
// * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with
//
// Uncaught Invalid dimensions for plot, width = 0, height = 0
// * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value'
var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]);
if (!this.plot && (!areWeVisible || this.model.currentDocuments.length == 0)) {
return
}
// create this.plot and cache it
if (!this.plot) {
// only lines for the present
options = {
id: 'line',
name: 'Line Chart'
};
this.plot = $.plot(this.$graph, this.createSeries(), options);
}
this.plot.setData(this.createSeries());
this.plot.resize();
this.plot.setupGrid();
this.plot.draw();
},
_getEditorData: function() {
$editor = this
var series = this.$series.map(function () {
return $(this).val();
});
this.chartConfig.series = $.makeArray(series)
this.chartConfig.group = this.el.find('.editor-group select').val();
},
createSeries: function () {
@ -676,19 +793,51 @@ my.FlotGraph = Backbone.View.extend({
return series;
},
// TODO: finish porting this function
addSeries: function () {
var element = this.seriesClone.clone(),
// Public: Adds a new empty series select box to the editor.
//
// All but the first select box will have a remove button that allows them
// to be removed.
//
// Returns itself.
addSeries: function (e) {
e.preventDefault();
var element = this.$seriesClone.clone(),
label = element.find('label'),
index = this.series.length;
this.el.$series.parent().find('ul').append(element);
this.updateSeries();
label.append('<a href="#remove">Remove</a>');
label.find('span').text(String.fromCharCode(this.series.length + 64));
index = this.$series.length;
this.el.find('.editor-series-group').append(element);
this._updateSeries();
label.append(' [<a href="#remove" class="action-remove-series">Remove</a>]');
label.find('span').text(String.fromCharCode(this.$series.length + 64));
return this;
},
// Public: Removes a series list item from the editor.
//
// Also updates the labels of the remaining series elements.
removeSeries: function (e) {
e.preventDefault();
var $el = $(e.target);
$el.parent().parent().remove();
this._updateSeries();
this.$series.each(function (index) {
if (index > 0) {
var labelSpan = $(this).prev().find('span');
labelSpan.text(String.fromCharCode(index + 65));
}
});
this.onEditorSubmit();
},
toggleHelp: function() {
this.el.find('.editor-info').toggleClass('editor-hide-info');
},
// Private: Resets the series property to reference the select elements.
//
// Returns itself.
_updateSeries: function () {
this.$series = this.el.find('.editor-series select');
}
});

View File

@ -31,12 +31,12 @@ test('new Dataset', function () {
dataset.fetch().then(function(dataset) {
equal(dataset.get('name'), metadata.name);
deepEqual(dataset.get('headers'), indata.headers);
equal(dataset.getLength(), 6);
dataset.getRows(4, 2).then(function(documentList) {
equal(dataset.docCount, 6);
dataset.getDocuments(4, 2).then(function(documentList) {
deepEqual(indata.rows[2], documentList.models[0].toJSON());
});
dataset.getRows().then(function(docList) {
// Test getRows
dataset.getDocuments().then(function(docList) {
// Test getDocuments
equal(docList.length, Math.min(10, indata.rows.length));
var doc1 = docList.models[0];
deepEqual(doc1.toJSON(), indata.rows[0]);
@ -158,8 +158,8 @@ test('Webstore Backend', function() {
dataset.fetch().then(function(dataset) {
deepEqual(['__id__', 'date', 'geometry', 'amount'], dataset.get('headers'));
equal(3, dataset.rowCount)
dataset.getRows().then(function(docList) {
equal(3, dataset.docCount)
dataset.getDocuments().then(function(docList) {
equal(3, docList.length)
equal("2009-01-01", docList.models[0].get('date'));
});

View File

@ -0,0 +1,113 @@
/* ==========================================================
* bootstrap-alerts.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#alerts
* ==========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ){
"use strict"
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
* ======================================================= */
var transitionEnd
$(document).ready(function () {
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
return support
})()
// set CSS transition event type
if ( $.support.transition ) {
transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
}
})
/* ALERT CLASS DEFINITION
* ====================== */
var Alert = function ( content, options ) {
this.settings = $.extend({}, $.fn.alert.defaults, options)
this.$element = $(content)
.delegate(this.settings.selector, 'click', this.close)
}
Alert.prototype = {
close: function (e) {
var $element = $(this).parent('.alert-message')
e && e.preventDefault()
$element.removeClass('in')
function removeElement () {
$element.remove()
}
$.support.transition && $element.hasClass('fade') ?
$element.bind(transitionEnd, removeElement) :
removeElement()
}
}
/* ALERT PLUGIN DEFINITION
* ======================= */
$.fn.alert = function ( options ) {
if ( options === true ) {
return this.data('alert')
}
return this.each(function () {
var $this = $(this)
if ( typeof options == 'string' ) {
return $this.data('alert')[options]()
}
$(this).data('alert', new Alert( this, options ))
})
}
$.fn.alert.defaults = {
selector: '.close'
}
$(document).ready(function () {
new Alert($('body'), {
selector: '.alert-message[data-alert] .close'
})
})
}( window.jQuery || window.ender );

2467
vendor/bootstrap/1.4.0/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff