[build/dist][s]: build current.

This commit is contained in:
Rufus Pollock 2013-05-05 20:09:11 +01:00
parent 5e9e7aacba
commit 773e03c9c7
3 changed files with 1 additions and 946 deletions

73
dist/recline.css vendored
View File

@ -24,42 +24,6 @@
opacity: 0.8 !important;
border: 1px solid #fdd !important;
}
.recline-graph .graph {
height: 500px;
}
.recline-graph .legend table {
width: auto;
margin-bottom: 0;
}
.recline-graph .legend td {
padding: 5px;
line-height: 13px;
}
.recline-graph .graph .alert {
width: 450px;
}
.flotr-mouse-value {
background-color: #FEE !important;
color: #000000 !important;
opacity: 0.8 !important;
border: 1px solid #fdd !important;
}
.flotr-legend {
border: none !important;
}
.flotr-legend-bg {
display: none;
}
.flotr-legend-color-box {
padding: 5px;
}
/**********************************************************
* (Data) Grid
*********************************************************/
@ -653,40 +617,3 @@ classes should alter those!
.recline-timeline {
position: relative;
}
.recline-transform {
overflow: hidden;
}
.recline-transform .script textarea {
width: 100%;
height: 100px;
font-family: monospace;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.recline-transform h2 {
margin-bottom: 10px;
}
.recline-transform h2 .okButton {
margin-left: 10px;
margin-top: -2px;
}
.expression-preview-parsing-status {
color: #999;
}
.expression-preview-parsing-status.error {
color: red;
}
.recline-transform .before-after .after {
font-style: italic;
}
.recline-transform .before-after .after.different {
font-weight: bold;
}

View File

@ -159,20 +159,6 @@ my.Dataset = Backbone.Model.extend({
return this._store.save(this._changes, this.toJSON());
},
transform: function(editFunc) {
var self = this;
if (!this._store.transform) {
alert('Transform is not supported with this backend: ' + this.get('backend'));
return;
}
this.trigger('recline:flash', {message: "Updating all visible docs. This could take a while...", persist: true, loader: true});
this._store.transform(editFunc).done(function() {
// reload data as records have changed
self.query();
self.trigger('recline:flash', {message: "Records updated successfully"});
});
},
// ### query
//
// AJAX method with promise API to get records from the backend.
@ -829,18 +815,6 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
});
return facetResults;
};
this.transform = function(editFunc) {
var dfd = new Deferred();
// TODO: should we clone before mapping? Do not see the point atm.
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
self.records = _.filter(self.records, function(record) {
return record != null;
});
dfd.resolve();
return dfd.promise();
};
};
}(this.recline.Backend.Memory));

848
dist/recline.js vendored
View File

@ -1,158 +1,5 @@
this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
(function(my) {
// ## CKAN Backend
//
// This provides connection to the CKAN DataStore (v2)
//
// General notes
//
// We need 2 things to make most requests:
//
// 1. CKAN API endpoint
// 2. ID of resource for which request is being made
//
// There are 2 ways to specify this information.
//
// EITHER (checked in order):
//
// * Every dataset must have an id equal to its resource id on the CKAN instance
// * The dataset has an endpoint attribute pointing to the CKAN API endpoint
//
// OR:
//
// Set the url attribute of the dataset to point to the Resource on the CKAN instance. The endpoint and id will then be automatically computed.
my.__type__ = 'ckan';
// private - use either jQuery or Underscore Deferred depending on what is available
var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;
// Default CKAN API endpoint used for requests (you can change this but it will affect every request!)
//
// DEPRECATION: this will be removed in v0.7. Please set endpoint attribute on dataset instead
my.API_ENDPOINT = 'http://datahub.io/api';
// ### fetch
my.fetch = function(dataset) {
var wrapper;
if (dataset.endpoint) {
wrapper = my.DataStore(dataset.endpoint);
} else {
var out = my._parseCkanResourceUrl(dataset.url);
dataset.id = out.resource_id;
wrapper = my.DataStore(out.endpoint);
}
var dfd = new Deferred();
var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0});
jqxhr.done(function(results) {
// map ckan types to our usual types ...
var fields = _.map(results.result.fields, function(field) {
field.type = field.type in CKAN_TYPES_MAP ? CKAN_TYPES_MAP[field.type] : field.type;
return field;
});
var out = {
fields: fields,
useMemoryStore: false
};
dfd.resolve(out);
});
return dfd.promise();
};
// only put in the module namespace so we can access for tests!
my._normalizeQuery = function(queryObj, dataset) {
var actualQuery = {
resource_id: dataset.id,
q: queryObj.q,
filters: {},
limit: queryObj.size || 10,
offset: queryObj.from || 0
};
if (queryObj.sort && queryObj.sort.length > 0) {
var _tmp = _.map(queryObj.sort, function(sortObj) {
return sortObj.field + ' ' + (sortObj.order || '');
});
actualQuery.sort = _tmp.join(',');
}
if (queryObj.filters && queryObj.filters.length > 0) {
_.each(queryObj.filters, function(filter) {
if (filter.type === "term") {
actualQuery.filters[filter.field] = filter.term;
}
});
}
return actualQuery;
};
my.query = function(queryObj, dataset) {
var wrapper;
if (dataset.endpoint) {
wrapper = my.DataStore(dataset.endpoint);
} else {
var out = my._parseCkanResourceUrl(dataset.url);
dataset.id = out.resource_id;
wrapper = my.DataStore(out.endpoint);
}
var actualQuery = my._normalizeQuery(queryObj, dataset);
var dfd = new Deferred();
var jqxhr = wrapper.search(actualQuery);
jqxhr.done(function(results) {
var out = {
total: results.result.total,
hits: results.result.records
};
dfd.resolve(out);
});
return dfd.promise();
};
// ### DataStore
//
// Simple wrapper around the CKAN DataStore API
//
// @param endpoint: CKAN api endpoint (e.g. http://datahub.io/api)
my.DataStore = function(endpoint) {
var that = {endpoint: endpoint || my.API_ENDPOINT};
that.search = function(data) {
var searchUrl = that.endpoint + '/3/action/datastore_search';
var jqxhr = jQuery.ajax({
url: searchUrl,
type: 'POST',
data: JSON.stringify(data)
});
return jqxhr;
};
return that;
};
// Parse a normal CKAN resource URL and return API endpoint etc
//
// Normal URL is something like http://demo.ckan.org/dataset/some-dataset/resource/eb23e809-ccbb-4ad1-820a-19586fc4bebd
my._parseCkanResourceUrl = function(url) {
parts = url.split('/');
var len = parts.length;
return {
resource_id: parts[len-1],
endpoint: parts.slice(0,[len-4]).join('/') + '/api'
};
};
var CKAN_TYPES_MAP = {
'int4': 'integer',
'int8': 'integer',
'float8': 'float'
};
}(this.recline.Backend.Ckan));
this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.CSV = this.recline.Backend.CSV || {};
// Note that provision of jQuery is optional (it is **only** needed if you use fetch on a remote file)
@ -450,7 +297,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
(function(my) {
my.__type__ = 'dataproxy';
// URL for the dataproxy
my.dataproxy_url = 'http://jsonpdataproxy.appspot.com';
my.dataproxy_url = '//jsonpdataproxy.appspot.com';
// Timeout for dataproxy (after this time if no response we error)
// Needed because use JSONP so do not receive e.g. 500 errors
my.timeout = 5000;
@ -1202,88 +1049,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
});
return facetResults;
};
this.transform = function(editFunc) {
var dfd = new Deferred();
// TODO: should we clone before mapping? Do not see the point atm.
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
self.records = _.filter(self.records, function(record) {
return record != null;
});
dfd.resolve();
return dfd.promise();
};
};
}(this.recline.Backend.Memory));
this.recline = this.recline || {};
this.recline.Data = this.recline.Data || {};
(function(my) {
// adapted from https://github.com/harthur/costco. heather rules
my.Transform = {};
my.Transform.evalFunction = function(funcString) {
try {
eval("var editFunc = " + funcString);
} catch(e) {
return {errorMessage: e+""};
}
return editFunc;
};
my.Transform.previewTransform = function(docs, editFunc, currentColumn) {
var preview = [];
var updated = my.Transform.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: before[currentColumn], after: after[currentColumn]});
} else {
preview.push({before: before, after: after});
}
}
return preview;
};
my.Transform.mapDocs = function(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 {
updates: edited,
docs: updatedDocs,
deletes: deleted,
failed: failed
};
};
}(this.recline.Data))
// This file adds in full array method support in browsers that don't support it
// see: http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc
@ -1511,20 +1279,6 @@ my.Dataset = Backbone.Model.extend({
return this._store.save(this._changes, this.toJSON());
},
transform: function(editFunc) {
var self = this;
if (!this._store.transform) {
alert('Transform is not supported with this backend: ' + this.get('backend'));
return;
}
this.trigger('recline:flash', {message: "Updating all visible docs. This could take a while...", persist: true, loader: true});
this._store.transform(editFunc).done(function() {
// reload data as records have changed
self.query();
self.trigger('recline:flash', {message: "Records updated successfully"});
});
},
// ### query
//
// AJAX method with promise API to get records from the backend.
@ -2449,468 +2203,6 @@ my.FlotControls = Backbone.View.extend({
});
})(jQuery, recline.View);
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## Graph view for a Dataset using Flotr2 graphing library.
//
// Initialization arguments (in a hash in first parameter):
//
// * model: recline.Model.Dataset
// * state: (optional) configuration hash of form:
//
// {
// group: {column name for x-axis},
// series: [{column name for series A}, {column name series B}, ... ],
// graphType: 'line',
// graphOptions: {custom [Flotr2 options](http://www.humblesoftware.com/flotr2/documentation#configuration)}
// }
//
// 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.Flotr2 = Backbone.View.extend({
template: ' \
<div class="recline-graph"> \
<div class="panel graph" style="display: block;"> \
<div class="js-temp-notice alert alert-block"> \
<h3 class="alert-heading">Hey there!</h3> \
<p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
<p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
</div> \
</div> \
</div> \
',
initialize: function(options) {
var self = this;
this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
this.el = $(this.el);
_.bindAll(this, 'render', 'redraw');
this.needToRedraw = false;
this.model.bind('change', this.render);
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.model.records.bind('add', this.redraw);
this.model.records.bind('reset', this.redraw);
var stateData = _.extend({
group: null,
// so that at least one series chooser box shows up
series: [],
graphType: 'lines-and-points'
},
options.state
);
this.state = new recline.Model.ObjectState(stateData);
this.editor = new my.Flotr2Controls({
model: this.model,
state: this.state.toJSON()
});
this.editor.state.bind('change', function() {
self.state.set(self.editor.state.toJSON());
self.redraw();
});
this.elSidebar = this.editor.el;
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
$(this.el).html(htmls);
this.$graph = this.el.find('.panel.graph');
return this;
},
redraw: function() {
// There appear to be issues generating a Flotr2 graph if either:
// * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flotr2 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 ((!areWeVisible || this.model.records.length === 0)) {
this.needToRedraw = true;
return;
}
// check we have something to plot
if (this.state.get('group') && this.state.get('series')) {
// faff around with width because flot draws axes *outside* of the element width which means graph can get push down as it hits element next to it
this.$graph.width(this.el.width() - 20);
var series = this.createSeries();
var options = this.getGraphOptions(this.state.attributes.graphType);
this.plot = Flotr.draw(this.$graph.get(0), series, options);
}
},
show: function() {
// because we cannot redraw when hidden we may need to when becoming visible
if (this.needToRedraw) {
this.redraw();
}
},
// ### getGraphOptions
//
// Get options for Flotr2 Graph
//
// needs to be function as can depend on state
//
// @param typeId graphType id (lines, lines-and-points etc)
getGraphOptions: function(typeId) {
var self = this;
var tickFormatter = function (x) {
return getFormattedX(x);
};
// infoboxes on mouse hover on points/bars etc
var trackFormatter = function (obj) {
var x = obj.x;
var y = obj.y;
// it's horizontal so we have to flip
if (self.state.attributes.graphType === 'bars') {
var _tmp = x;
x = y;
y = _tmp;
}
x = getFormattedX(x);
var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
group: self.state.attributes.group,
x: x,
series: obj.series.label,
y: y
});
return content;
};
var getFormattedX = function (x) {
var xfield = self.model.fields.get(self.state.attributes.group);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (self.model.records.models[parseInt(x)]) {
x = self.model.records.models[parseInt(x)].get(self.state.attributes.group);
if (isDateTime) {
x = new Date(x).toLocaleDateString();
}
} else if (isDateTime) {
x = new Date(parseInt(x)).toLocaleDateString();
}
return x;
}
var xaxis = {};
xaxis.tickFormatter = tickFormatter;
var yaxis = {};
yaxis.autoscale = true;
yaxis.autoscaleMargin = 0.02;
var mouse = {};
mouse.track = true;
mouse.relative = true;
mouse.trackFormatter = trackFormatter;
var legend = {};
legend.position = 'ne';
// mouse.lineColor is set in createSeries
var optionsPerGraphType = {
lines: {
legend: legend,
colors: this.graphColors,
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
mouse: mouse
},
points: {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
xaxis: xaxis,
yaxis: yaxis,
mouse: mouse,
grid: { hoverable: true, clickable: true }
},
'lines-and-points': {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
mouse: mouse,
grid: { hoverable: true, clickable: true }
},
bars: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: yaxis,
yaxis: xaxis,
mouse: {
track: true,
relative: true,
trackFormatter: trackFormatter,
fillColor: '#FFFFFF',
fillOpacity: 0.3,
position: 'e'
},
bars: {
show: true,
horizontal: true,
shadowSize: 0,
barWidth: 0.8
}
},
columns: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: xaxis,
yaxis: yaxis,
mouse: {
track: true,
relative: true,
trackFormatter: trackFormatter,
fillColor: '#FFFFFF',
fillOpacity: 0.3,
position: 'n'
},
bars: {
show: true,
horizontal: false,
shadowSize: 0,
barWidth: 0.8
}
},
grid: { hoverable: true, clickable: true }
};
if (self.state.get('graphOptions')){
return _.extend(optionsPerGraphType[typeId],
self.state.get('graphOptions')
)
}else{
return optionsPerGraphType[typeId];
}
},
createSeries: function() {
var self = this;
var series = [];
_.each(this.state.attributes.series, function(field) {
var points = [];
_.each(self.model.records.models, function(doc, index) {
var xfield = self.model.fields.get(self.state.attributes.group);
var x = doc.getFieldValue(xfield);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (isDateTime) {
// datetime
if (self.state.attributes.graphType != 'bars' && self.state.attributes.graphType != 'columns') {
// not bar or column
x = new Date(x).getTime();
} else {
// bar or column
x = index;
}
} else if (typeof x === 'string') {
// string
x = parseFloat(x);
if (isNaN(x)) {
x = index;
}
}
var yfield = self.model.fields.get(field);
var y = doc.getFieldValue(yfield);
// horizontal bar chart
if (self.state.attributes.graphType == 'bars') {
points.push([y, x]);
} else {
points.push([x, y]);
}
});
series.push({data: points, label: field, mouse:{lineColor: self.graphColors[series.length]}});
});
return series;
}
});
my.Flotr2Controls = Backbone.View.extend({
className: "editor",
template: ' \
<div class="editor"> \
<form class="form-stacked"> \
<div class="clearfix"> \
<label>Graph Type</label> \
<div class="input editor-type"> \
<select> \
<option value="lines-and-points">Lines and Points</option> \
<option value="lines">Lines</option> \
<option value="points">Points</option> \
<option value="bars">Bars</option> \
<option value="columns">Columns</option> \
</select> \
</div> \
<label>Group Column (Axis 1)</label> \
<div class="input editor-group"> \
<select> \
<option value="">Please choose ...</option> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
<div class="editor-series-group"> \
</div> \
</div> \
<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> \
',
templateSeriesEditor: ' \
<div class="editor-series js-series-{{seriesIndex}}"> \
<label>Series <span>{{seriesName}} (Axis 2)</span> \
[<a href="#remove" class="action-remove-series">Remove</a>] \
</label> \
<div class="input"> \
<select> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
</div> \
',
events: {
'change form select': 'onEditorSubmit',
'click .editor-add': '_onAddSeries',
'click .action-remove-series': 'removeSeries'
},
initialize: function(options) {
var self = this;
this.el = $(this.el);
_.bindAll(this, 'render');
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.state = new recline.Model.ObjectState(options.state);
this.render();
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
this.el.html(htmls);
// set up editor from state
if (this.state.get('graphType')) {
this._selectOption('.editor-type', this.state.get('graphType'));
}
if (this.state.get('group')) {
this._selectOption('.editor-group', this.state.get('group'));
}
// ensure at least one series box shows up
var tmpSeries = [""];
if (this.state.get('series').length > 0) {
tmpSeries = this.state.get('series');
}
_.each(tmpSeries, function(series, idx) {
self.addSeries(idx);
self._selectOption('.editor-series.js-series-' + idx, series);
});
return this;
},
// Private: Helper function to select an option from a select list
//
_selectOption: function(id,value){
var options = this.el.find(id + ' select > option');
if (options) {
options.each(function(opt){
if (this.value == value) {
$(this).attr('selected','selected');
return false;
}
});
}
},
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
var $editor = this;
var $series = this.el.find('.editor-series select');
var series = $series.map(function () {
return $(this).val();
});
var updatedState = {
series: $.makeArray(series),
group: this.el.find('.editor-group select').val(),
graphType: this.el.find('.editor-type select').val()
};
this.state.set(updatedState);
},
// Public: Adds a new empty series select box to the editor.
//
// @param [int] idx index of this series in the list of series
//
// Returns itself.
addSeries: function (idx) {
var data = _.extend({
seriesIndex: idx,
seriesName: String.fromCharCode(idx + 64 + 1)
}, this.model.toTemplateJSON());
var htmls = Mustache.render(this.templateSeriesEditor, data);
this.el.find('.editor-series-group').append(htmls);
return this;
},
_onAddSeries: function(e) {
e.preventDefault();
this.addSeries(this.state.get('series').length);
},
// 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.onEditorSubmit();
}
});
})(jQuery, recline.View);
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
this.recline.View.Graph = this.recline.View.Flot;
@ -4003,12 +3295,6 @@ my.MultiView = Backbone.View.extend({
model: this.model,
state: this.state.get('view-timeline')
})
}, {
id: 'transform',
label: 'Transform',
view: new my.Transform({
model: this.model
})
}];
}
// Hashes of sidebar elements
@ -4947,138 +4233,6 @@ my.Timeline = Backbone.View.extend({
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
// Views module following classic module pattern
(function($, my) {
// ## ColumnTransform
//
// View (Dialog) for doing data transformations
my.Transform = Backbone.View.extend({
template: ' \
<div class="recline-transform"> \
<div class="script"> \
<h2> \
Transform Script \
<button class="okButton btn btn-primary">Run on all records</button> \
</h2> \
<textarea class="expression-preview-code"></textarea> \
</div> \
<div class="expression-preview-parsing-status"> \
No syntax error. \
</div> \
<div class="preview"> \
<h3>Preview</h3> \
<div class="expression-preview-container"></div> \
</div> \
</div> \
',
events: {
'click .okButton': 'onSubmit',
'keydown .expression-preview-code': 'onEditorKeydown'
},
initialize: function(options) {
this.el = $(this.el);
},
render: function() {
var htmls = Mustache.render(this.template);
this.el.html(htmls);
// Put in the basic (identity) transform script
// TODO: put this into the template?
var editor = this.el.find('.expression-preview-code');
if (this.model.fields.length > 0) {
var col = this.model.fields.models[0].id;
} else {
var col = 'unknown';
}
editor.val("function(doc) {\n doc['"+ col +"'] = doc['"+ col +"'];\n return doc;\n}");
editor.keydown();
},
onSubmit: function(e) {
var self = this;
var funcText = this.el.find('.expression-preview-code').val();
var editFunc = recline.Data.Transform.evalFunction(funcText);
if (editFunc.errorMessage) {
this.trigger('recline:flash', {message: "Error with function! " + editFunc.errorMessage});
return;
}
this.model.transform(editFunc);
},
editPreviewTemplate: ' \
<table class="table table-condensed table-bordered before-after"> \
<thead> \
<tr> \
<th>Field</th> \
<th>Before</th> \
<th>After</th> \
</tr> \
</thead> \
<tbody> \
{{#row}} \
<tr> \
<td> \
{{field}} \
</td> \
<td class="before {{#different}}different{{/different}}"> \
{{before}} \
</td> \
<td class="after {{#different}}different{{/different}}"> \
{{after}} \
</td> \
</tr> \
{{/row}} \
</tbody> \
</table> \
',
onEditorKeydown: function(e) {
var self = this;
// if you don't setTimeout it won't grab the latest character if you call e.target.value
window.setTimeout( function() {
var errors = self.el.find('.expression-preview-parsing-status');
var editFunc = recline.Data.Transform.evalFunction(e.target.value);
if (!editFunc.errorMessage) {
errors.text('No syntax error.');
var docs = self.model.records.map(function(doc) {
return doc.toJSON();
});
var previewData = recline.Data.Transform.previewTransform(docs, editFunc);
var $el = self.el.find('.expression-preview-container');
var fields = self.model.fields.toJSON();
var rows = _.map(previewData.slice(0,4), function(row) {
return _.map(fields, function(field) {
return {
field: field.id,
before: row.before[field.id],
after: row.after[field.id],
different: !_.isEqual(row.before[field.id], row.after[field.id])
}
});
});
$el.html('');
_.each(rows, function(row) {
var templated = Mustache.render(self.editPreviewTemplate, {
row: row
});
$el.append(templated);
});
} else {
errors.text(editFunc.errorMessage);
}
}, 1, true);
}
});
})(jQuery, recline.View);
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## FacetViewer