this . recline = this . recline || {};
+ backend.csv.js
backend.csv.js 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)
fetch
+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)
( function ( my ) {
+ my . __type__ = 'csv' ; use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; fetch
fetch supports 3 options depending on the attribute provided on the dataset argument
dataset.file
: file
is an HTML5 file object. This is opened and parsed with the CSV parser.
dataset.data
: data
is a string in CSV format. This is passed directly to the CSV parser
-dataset.url
: a url to an online CSV file that is ajax accessible (note this usually requires either local or on a server that is CORS enabled). The file is then loaded using $.ajax and parsed using the CSV parser (NB: this requires jQuery)
+dataset.url
: a url to an online CSV file that is ajax accessible (note this usually requires either local or on a server that is CORS enabled). The file is then loaded using jQuery.ajax and parsed using the CSV parser (NB: this requires jQuery)
All options generates similar data and use the memory store outcome, that is they return something like:
@@ -19,7 +20,7 @@
useMemoryStore: true
}
my . fetch = function ( dataset ) {
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
if ( dataset . file ) {
var reader = new FileReader ();
var encoding = dataset . encoding || 'UTF-8' ;
@@ -44,7 +45,7 @@
useMemoryStore : true
});
} else if ( dataset . url ) {
- $ . get ( dataset . url ). done ( function ( data ) {
+ jQuery . get ( dataset . url ). done ( function ( data ) {
var rows = my . parseCSV ( data , dataset );
dfd . resolve ({
records : rows ,
@@ -53,7 +54,7 @@
});
}
return dfd . promise ();
- }; parseCSV
+ }; parseCSV
Converts a Comma Separated Values string into an array of arrays.
Each line in the CSV becomes an array.
@@ -74,7 +75,7 @@
quotechar, or which contain new-line characters. It defaults to '"'
Heavily based on uselesscode's JS CSV parser (MIT Licensed):
-http://www.uselesscode.org/javascript/csv/
my . parseCSV = function ( s , options ) { Get rid of any trailing \n
s = chomp ( s );
+http://www.uselesscode.org/javascript/csv/ my . parseCSV = function ( s , options ) { Get rid of any trailing \n
s = chomp ( s );
var options = options || {};
var trm = ( options . trim === false ) ? false : true ;
@@ -91,10 +92,10 @@
processField ;
processField = function ( field ) {
- if ( fieldQuoted !== true ) { If field is empty set to null
if ( field === '' ) {
- field = null ; If the field was not quoted and we are trimming fields, trim it
} else if ( trm === true ) {
+ if ( fieldQuoted !== true ) { If field is empty set to null
if ( field === '' ) {
+ field = null ; If the field was not quoted and we are trimming fields, trim it
} else if ( trm === true ) {
field = trim ( field );
- } Convert unquoted numbers to their appropriate types
if ( rxIsInt . test ( field )) {
+ } Convert unquoted numbers to their appropriate types
if ( rxIsInt . test ( field )) {
field = parseInt ( field , 10 );
} else if ( rxIsFloat . test ( field )) {
field = parseFloat ( field , 10 );
@@ -104,30 +105,30 @@
};
for ( i = 0 ; i < s . length ; i += 1 ) {
- cur = s . charAt ( i ); If we are at a EOF or EOR
if ( inQuote === false && ( cur === delimiter || cur === "\n" )) {
- field = processField ( field ); Add the current field to the current row
If this is EOR append row to output and flush row
if ( cur === "\n" ) {
+ cur = s . charAt ( i ); If we are at a EOF or EOR
if ( inQuote === false && ( cur === delimiter || cur === "\n" )) {
+ field = processField ( field ); Add the current field to the current row
If this is EOR append row to output and flush row
if ( cur === "\n" ) {
out . push ( row );
row = [];
- } Flush the field buffer
Flush the field buffer
field = '' ;
fieldQuoted = false ;
- } else { If it's not a quotechar, add it to the field buffer
if ( cur !== quotechar ) {
+ } else { If it's not a quotechar, add it to the field buffer
if ( cur !== quotechar ) {
field += cur ;
} else {
- if ( ! inQuote ) { We are not in a quote, start a quote
inQuote = true ;
+ if ( ! inQuote ) { We are not in a quote, start a quote
inQuote = true ;
fieldQuoted = true ;
- } else { Next char is quotechar, this is an escaped quotechar
if ( s . charAt ( i + 1 ) === quotechar ) {
- field += quotechar ; Skip the next char
It's not escaping, so end quote
inQuote = false ;
+ } else { Next char is quotechar, this is an escaped quotechar
if ( s . charAt ( i + 1 ) === quotechar ) {
+ field += quotechar ; Skip the next char
It's not escaping, so end quote
inQuote = false ;
}
}
}
}
- } Add the last field
field = processField ( field );
+ } Add the last field
field = processField ( field );
row . push ( field );
out . push ( row );
return out ;
- }; serializeCSV
+ }; serializeCSV
Convert an Object or a simple array of arrays into a Comma
Separated Values string.
@@ -179,9 +180,9 @@
processField ;
processField = function ( field ) {
- if ( field === null ) { If field is null set to empty string
field = '' ;
- } else if ( typeof field === "string" && rxNeedsQuoting . test ( field )) { Convert string to delimited string
field = quotechar + field + quotechar ;
- } else if ( typeof field === "number" ) { Convert number to string
field = field . toString ( 10 );
+ if ( field === null ) { If field is null set to empty string
field = '' ;
+ } else if ( typeof field === "string" && rxNeedsQuoting . test ( field )) { Convert string to delimited string
field = quotechar + field + quotechar ;
+ } else if ( typeof field === "number" ) { Convert number to string
field = field . toString ( 10 );
}
return field ;
@@ -191,12 +192,12 @@
cur = a [ i ];
for ( j = 0 ; j < cur . length ; j += 1 ) {
- field = processField ( cur [ j ]); If this is EOR append row to output and flush row
if ( j === ( cur . length - 1 )) {
+ field = processField ( cur [ j ]); If this is EOR append row to output and flush row
if ( j === ( cur . length - 1 )) {
row += field ;
out += row + "\n" ;
row = '' ;
- } else { Add the current field to the current row
row += field + delimiter ;
- } Flush the field buffer
Add the current field to the current row
row += field + delimiter ;
+ } Flush the field buffer
field = '' ;
}
}
@@ -204,10 +205,10 @@
};
var rxIsInt = /^\d+$/ ,
- rxIsFloat = /^\d*\.\d+$|^\d+\.\d*$/ , If a string has leading or trailing space,
+ rxIsFloat = /^\d*\.\d+$|^\d+\.\d*$/ ,
If a string has leading or trailing space,
contains a comma double quote or a newline
it needs to be quoted in CSV output
rxNeedsQuoting = /^\s|\s$|,|"|\n/ ,
- trim = ( function () { Fx 3.1 has a native trim function, it's about 10x faster, use it if it exists
if ( String . prototype . trim ) {
+ trim = ( function () { Fx 3.1 has a native trim function, it's about 10x faster, use it if it exists
if ( String . prototype . trim ) {
return function ( s ) {
return s . trim ();
};
@@ -219,12 +220,12 @@
}());
function chomp ( s ) {
- if ( s . charAt ( s . length - 1 ) !== "\n" ) { Does not end with \n, just return string
Remove the \n
return s . substring ( 0 , s . length - 1 );
+ if ( s . charAt ( s . length - 1 ) !== "\n" ) { Does not end with \n, just return string
Remove the \n
return s . substring ( 0 , s . length - 1 );
}
}
-}( this . recline . Backend . CSV , jQuery ));
+}( this . recline . Backend . CSV ));
\ No newline at end of file
diff --git a/docs/src/backend.dataproxy.html b/docs/src/backend.dataproxy.html
index cc30890c2..d52d17db8 100644
--- a/docs/src/backend.dataproxy.html
+++ b/docs/src/backend.dataproxy.html
@@ -1,10 +1,12 @@
- backend.dataproxy.js
backend.dataproxy.js this . recline = this . recline || {};
+ backend.dataproxy.js
backend.dataproxy.js this . recline = this . recline || {};
this . recline . Backend = this . recline . Backend || {};
this . recline . Backend . DataProxy = this . recline . Backend . DataProxy || {};
-( function ( $ , my ) {
- my . __type__ = 'dataproxy' ; URL for the dataproxy
my . dataproxy_url = 'http://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
load
+( function ( my ) {
+ my . __type__ = 'dataproxy' ; URL for the dataproxy
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
use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; load
Load data from a URL via the DataProxy .
@@ -14,12 +16,12 @@
'max-results' : dataset . size || dataset . rows || 1000 ,
type : dataset . format || ''
};
- var jqxhr = $ . ajax ({
+ var jqxhr = jQuery . ajax ({
url : my . dataproxy_url ,
data : data ,
dataType : 'jsonp'
});
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
_wrapInTimeout ( jqxhr ). done ( function ( results ) {
if ( results . error ) {
dfd . reject ( results . error );
@@ -31,33 +33,33 @@
useMemoryStore : true
});
})
- . fail ( function ( arguments ) {
- dfd . reject ( arguments );
+ . fail ( function ( args ) {
+ dfd . reject ( args );
});
return dfd . promise ();
- }; _wrapInTimeout
+ }; _wrapInTimeout
Convenience method providing a crude way to catch backend errors on JSONP calls.
Many of backends use JSONP and so will not get error messages and this is
a crude way to catch those errors.
var _wrapInTimeout = function ( ourFunction ) {
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
var timer = setTimeout ( function () {
dfd . reject ({
message : 'Request Error: Backend did not respond after ' + ( my . timeout / 1000 ) + ' seconds'
});
}, my . timeout );
- ourFunction . done ( function ( arguments ) {
+ ourFunction . done ( function ( args ) {
clearTimeout ( timer );
- dfd . resolve ( arguments );
+ dfd . resolve ( args );
})
- . fail ( function ( arguments ) {
+ . fail ( function ( args ) {
clearTimeout ( timer );
- dfd . reject ( arguments );
+ dfd . reject ( args );
})
;
return dfd . promise ();
- }
+ };
-}( jQuery , this . recline . Backend . DataProxy ));
+}( this . recline . Backend . DataProxy ));
\ No newline at end of file
diff --git a/docs/src/backend.elasticsearch.html b/docs/src/backend.elasticsearch.html
index 5927a78d7..614874586 100644
--- a/docs/src/backend.elasticsearch.html
+++ b/docs/src/backend.elasticsearch.html
@@ -1,9 +1,9 @@
- backend.elasticsearch.js
backend.elasticsearch.js this . recline = this . recline || {};
+ backend.elasticsearch.js
backend.elasticsearch.js this . recline = this . recline || {};
this . recline . Backend = this . recline . Backend || {};
this . recline . Backend . ElasticSearch = this . recline . Backend . ElasticSearch || {};
( function ( $ , my ) {
- my . __type__ = 'elasticsearch' ; ElasticSearch Wrapper
+ my . __type__ = 'elasticsearch' ; use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; ElasticSearch Wrapper
A simple JS wrapper around an ElasticSearch endpoints.
@@ -23,7 +23,7 @@
this . options = _ . extend ({
dataType : 'json'
},
- options ); mapping
+ options ); mapping
Get ES mapping for this type/table
@@ -34,7 +34,7 @@
dataType : this . options . dataType
});
return jqxhr ;
- }; get
+ }; get
Get record corresponding to specified id
@@ -44,7 +44,7 @@
url : base ,
dataType : 'json'
});
- }; upsert
+ }; upsert
create / update a record to ElasticSearch backend
@@ -61,7 +61,7 @@
data : data ,
dataType : 'json'
});
- }; delete
+ }; delete
Delete a record from the ElasticSearch backend.
@@ -104,7 +104,7 @@
});
}
return out ;
- }, convert from Recline sort structure to ES form
+ },
convert from Recline sort structure to ES form
http://www.elasticsearch.org/guide/reference/api/search/sort.html
this . _normalizeSort = function ( sort ) {
var out = _ . map ( sort , function ( sortObj ) {
var _tmp = {};
@@ -127,7 +127,7 @@
out . geo_distance . unit = filter . unit ;
}
return out ;
- }, query
+ }, query
@return deferred supporting promise API
this . query = function ( queryObj ) {
var esQuery = ( queryObj && queryObj . toJSON ) ? queryObj . toJSON () : _ . extend ({}, queryObj );
@@ -146,18 +146,18 @@
});
return jqxhr ;
}
- }; Recline Connectors
+ }; Recline Connectors
Requires URL of ElasticSearch endpoint to be specified on the dataset
-via the url attribute.
ES options which are passed through to options
on Wrapper (see Wrapper for details)
fetch my . fetch = function ( dataset ) {
+via the url attribute. ES options which are passed through to options
on Wrapper (see Wrapper for details)
fetch my . fetch = function ( dataset ) {
var es = new my . Wrapper ( dataset . url , my . esOptions );
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
es . mapping (). done ( function ( schema ) {
if ( ! schema ){
dfd . reject ({ 'message' : 'Elastic Search did not return a mapping' });
return ;
- } only one top level key in ES = the type so we can ignore it
var key = _ . keys ( schema )[ 0 ];
+ } only one top level key in ES = the type so we can ignore it
var key = _ . keys ( schema )[ 0 ];
var fieldData = _ . map ( schema [ key ]. properties , function ( dict , fieldName ) {
dict . id = fieldName ;
return dict ;
@@ -170,10 +170,10 @@
dfd . reject ( arguments );
});
return dfd . promise ();
- }; save my . save = function ( changes , dataset ) {
+ }; save my . save = function ( changes , dataset ) {
var es = new my . Wrapper ( dataset . url , my . esOptions );
if ( changes . creates . length + changes . updates . length + changes . deletes . length > 1 ) {
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
msg = 'Saving more than one item at a time not yet supported' ;
alert ( msg );
dfd . reject ( msg );
@@ -187,8 +187,8 @@
} else if ( changes . deletes . length > 0 ) {
return es . remove ( changes . deletes [ 0 ]. id );
}
- }; query my . query = function ( queryObj , dataset ) {
- var dfd = $ . Deferred ();
+ }; query my . query = function ( queryObj , dataset ) {
+ var dfd = new Deferred ();
var es = new my . Wrapper ( dataset . url , my . esOptions );
var jqxhr = es . query ( queryObj );
jqxhr . done ( function ( results ) {
@@ -213,7 +213,7 @@
dfd . reject ( out );
});
return dfd . promise ();
- }; makeRequest
+ }; makeRequest
Just $.ajax but in any headers in the 'headers' attribute of this
Backend instance. Example:
diff --git a/docs/src/backend.gdocs.html b/docs/src/backend.gdocs.html
index 25c6a7c03..7d07578d2 100644
--- a/docs/src/backend.gdocs.html
+++ b/docs/src/backend.gdocs.html
@@ -1,9 +1,9 @@
- backend.gdocs.js
backend.gdocs.js this . recline = this . recline || {};
+ backend.gdocs.js
backend.gdocs.js this . recline = this . recline || {};
this . recline . Backend = this . recline . Backend || {};
this . recline . Backend . GDocs = this . recline . Backend . GDocs || {};
-( function ( $ , my ) {
- my . __type__ = 'gdocs' ; Google spreadsheet backend
+( function ( my ) {
+ my . __type__ = 'gdocs' ; use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; Google spreadsheet backend
Fetch data from a Google Docs spreadsheet.
@@ -29,19 +29,19 @@
fields: array of Field objects
records: array of objects for each row
my . fetch = function ( dataset ) {
- var dfd = $ . Deferred ();
- var urls = my . getGDocsAPIUrls ( dataset . url ); TODO cover it with tests
+ var dfd = new Deferred ();
+ var urls = my . getGDocsAPIUrls ( dataset . url );
TODO cover it with tests
get the spreadsheet title
( function () {
- var titleDfd = $ . Deferred ();
+ var titleDfd = new Deferred ();
- $ . getJSON ( urls . spreadsheet , function ( d ) {
+ jQuery . getJSON ( urls . spreadsheet , function ( d ) {
titleDfd . resolve ({
spreadsheetTitle : d . feed . title . $t
});
});
return titleDfd . promise ();
- }()). then ( function ( response ) { get the actual worksheet data
$ . getJSON ( urls . worksheet , function ( d ) {
+ }()). then ( function ( response ) { get the actual worksheet data
jQuery . getJSON ( urls . worksheet , function ( d ) {
var result = my . parseData ( d );
var fields = _ . map ( result . fields , function ( fieldId ) {
return { id : fieldId };
@@ -61,7 +61,7 @@
});
return dfd . promise ();
- }; parseData
+ }; parseData
Parse data from Google Docs API into a reasonable form
@@ -79,20 +79,20 @@
};
var entries = gdocsSpreadsheet . feed . entry || [];
var key ;
- var colName ; percentage values (e.g. 23.3%)
var rep = /^([\d\.\-]+)\%$/ ;
+ var colName ; percentage values (e.g. 23.3%)
var rep = /^([\d\.\-]+)\%$/ ;
- for ( key in entries [ 0 ]) { it's barely possible it has inherited keys starting with 'gsx$'
if ( /^gsx/ . test ( key )) {
+ for ( key in entries [ 0 ]) { it's barely possible it has inherited keys starting with 'gsx$'
if ( /^gsx/ . test ( key )) {
colName = key . substr ( 4 );
results . fields . push ( colName );
}
- } converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
results . records = _ . map ( entries , function ( entry ) {
+ } converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
results . records = _ . map ( entries , function ( entry ) {
var row = {};
_ . each ( results . fields , function ( col ) {
var _keyname = 'gsx$' + col ;
var value = entry [ _keyname ]. $t ;
var num ;
- TODO cover this part of code with test
+
TODO cover this part of code with test
TODO use the regexp only once
if labelled as % and value contains %, convert
if ( colTypes [ col ] === 'percent' && rep . test ( value )) {
num = rep . exec ( value )[ 1 ];
@@ -107,20 +107,23 @@
results . worksheetTitle = gdocsSpreadsheet . feed . title . $t ;
return results ;
- }; Convenience function to get GDocs JSON API Url from standard URL
my . getGDocsAPIUrls = function ( url ) { https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=YYY
var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+).*gid=([\d]+).*/ ;
+ }; Convenience function to get GDocs JSON API Url from standard URL
my . getGDocsAPIUrls = function ( url ) { https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=YYY
var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+)[^#]*(#gid=([\d]+).*)?/ ;
var matches = url . match ( regex );
var key ;
var worksheet ;
var urls ;
if ( !! matches ) {
- key = matches [ 1 ]; the gid in url is 0-based and feed url is 1-based
worksheet = parseInt ( matches [ 2 ]) + 1 ;
+ key = matches [ 1 ]; the gid in url is 0-based and feed url is 1-based
worksheet = parseInt ( matches [ 3 ]) + 1 ;
+ if ( isNaN ( worksheet )) {
+ worksheet = 1 ;
+ }
urls = {
worksheet : 'https://spreadsheets.google.com/feeds/list/' + key + '/' + worksheet + '/public/values?alt=json' ,
spreadsheet : 'https://spreadsheets.google.com/feeds/worksheets/' + key + '/public/basic?alt=json'
}
}
- else { we assume that it's one of the feeds urls
by default then, take first worksheet
we assume that it's one of the feeds urls
by default then, take first worksheet
worksheet = 1 ;
urls = {
worksheet : 'https://spreadsheets.google.com/feeds/list/' + key + '/' + worksheet + '/public/values?alt=json' ,
spreadsheet : 'https://spreadsheets.google.com/feeds/worksheets/' + key + '/public/basic?alt=json'
@@ -129,6 +132,6 @@
return urls ;
};
-}( jQuery , this . recline . Backend . GDocs ));
+}( this . recline . Backend . GDocs ));
\ No newline at end of file
diff --git a/docs/src/backend.memory.html b/docs/src/backend.memory.html
index 3cdd26fcc..5585712e3 100644
--- a/docs/src/backend.memory.html
+++ b/docs/src/backend.memory.html
@@ -1,49 +1,49 @@
- backend.memory.js
backend.memory.js this . recline = this . recline || {};
+ backend.memory.js
backend.memory.js this . recline = this . recline || {};
this . recline . Backend = this . recline . Backend || {};
this . recline . Backend . Memory = this . recline . Backend . Memory || {};
-( function ( $ , my ) {
- my . __type__ = 'memory' ; Data Wrapper
+( function ( my ) {
+ my . __type__ = 'memory' ; private data - use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; Data Wrapper
Turn a simple array of JS objects into a mini data-store with
functionality like querying, faceting, updating (by ID) and deleting (by
ID).
-@param data list of hashes for each record/row in the data ({key:
+
@param records list of hashes for each record/row in the data ({key:
value, key: value})
@param fields (optional) list of field hashes (each hash defining a field
as per recline.Model.Field). If fields not specified they will be taken
-from the data.
my . Store = function ( data , fields ) {
+from the data. my . Store = function ( records , fields ) {
var self = this ;
- this . data = data ;
+ this . records = records ; backwards compatability (in v0.5 records was named data)
this . data = this . records ;
if ( fields ) {
this . fields = fields ;
} else {
- if ( data ) {
- this . fields = _ . map ( data [ 0 ], function ( value , key ) {
+ if ( records ) {
+ this . fields = _ . map ( records [ 0 ], function ( value , key ) {
return { id : key , type : 'string' };
});
}
}
this . update = function ( doc ) {
- _ . each ( self . data , function ( internalDoc , idx ) {
+ _ . each ( self . records , function ( internalDoc , idx ) {
if ( doc . id === internalDoc . id ) {
- self . data [ idx ] = doc ;
+ self . records [ idx ] = doc ;
}
});
};
this . remove = function ( doc ) {
- var newdocs = _ . reject ( self . data , function ( internalDoc ) {
+ var newdocs = _ . reject ( self . records , function ( internalDoc ) {
return ( doc . id === internalDoc . id );
});
- this . data = newdocs ;
+ this . records = newdocs ;
};
this . save = function ( changes , dataset ) {
var self = this ;
- var dfd = $ . Deferred (); TODO _.each(changes.creates) { ... }
_ . each ( changes . updates , function ( record ) {
+ var dfd = new Deferred (); TODO _.each(changes.creates) { ... }
_ . each ( changes . updates , function ( record ) {
self . update ( record );
});
_ . each ( changes . deletes , function ( record ) {
@@ -54,13 +54,13 @@
},
this . query = function ( queryObj ) {
- var dfd = $ . Deferred ();
- var numRows = queryObj . size || this . data . length ;
+ var dfd = new Deferred ();
+ var numRows = queryObj . size || this . records . length ;
var start = queryObj . from || 0 ;
- var results = this . data ;
+ var results = this . records ;
results = this . _applyFilters ( results , queryObj );
- results = this . _applyFreeTextQuery ( results , queryObj ); TODO: this is not complete sorting!
+ results = this . _applyFreeTextQuery ( results , queryObj );
TODO: this is not complete sorting!
What's wrong is we sort on the last entry in the sort list if there are multiple sort criteria
_ . each ( queryObj . sort , function ( sortObj ) {
var fieldName = sortObj . field ;
results = _ . sortBy ( results , function ( doc ) {
@@ -79,8 +79,8 @@
};
dfd . resolve ( out );
return dfd . promise ();
- }; in place filtering
this . _applyFilters = function ( results , queryObj ) {
- var filters = queryObj . filters ; register filters
var filterFunctions = {
+ }; in place filtering
this . _applyFilters = function ( results , queryObj ) {
+ var filters = queryObj . filters ; register filters
var filterFunctions = {
term : term ,
range : range ,
geo_distance : geo_distance
@@ -88,6 +88,7 @@
var dataParsers = {
integer : function ( e ) { return parseFloat ( e , 10 ); },
'float' : function ( e ) { return parseFloat ( e , 10 ); },
+ number : function ( e ) { return parseFloat ( e , 10 ); },
string : function ( e ) { return e . toString () },
date : function ( e ) { return new Date ( e ). valueOf () },
datetime : function ( e ) { return new Date ( e ). valueOf () }
@@ -99,11 +100,11 @@
function getDataParser ( filter ) {
var fieldType = keyedFields [ filter . field ]. type || 'string' ;
return dataParsers [ fieldType ];
- } filter records
return _ . filter ( results , function ( record ) {
+ } filter records
return _ . filter ( results , function ( record ) {
var passes = _ . map ( filters , function ( filter ) {
return filterFunctions [ filter . type ]( record , filter );
- }); return only these records that pass all filters
return _ . all ( passes , _ . identity );
- }); filters definitions
function term ( record , filter ) {
+ }); return only these records that pass all filters
return _ . all ( passes , _ . identity );
+ }); filters definitions
function term ( record , filter ) {
var parse = getDataParser ( filter );
var value = parse ( record [ filter . field ]);
var term = parse ( filter . term );
@@ -117,15 +118,15 @@
var parse = getDataParser ( filter );
var value = parse ( record [ filter . field ]);
var start = parse ( filter . start );
- var stop = parse ( filter . stop ); if at least one end of range is set do not allow '' to get through
+ var stop = parse ( filter . stop );
if at least one end of range is set do not allow '' to get through
note that for strings '' <= {any-character} e.g. '' <= 'a'
if (( ! startnull || ! stopnull ) && value === '' ) {
return false ;
}
return (( startnull || value >= start ) && ( stopnull || value <= stop ));
}
- function geo_distance () { TODO code here
we OR across fields but AND across terms in query string
this . _applyFreeTextQuery = function ( results , queryObj ) {
+ function geo_distance () { TODO code here
we OR across fields but AND across terms in query string
this . _applyFreeTextQuery = function ( results , queryObj ) {
if ( queryObj . q ) {
var terms = queryObj . q . split ( ' ' );
var patterns = _ . map ( terms , function ( term ) {
@@ -139,10 +140,10 @@
var value = rawdoc [ field . id ];
if (( value !== null ) && ( value !== undefined )) {
value = value . toString ();
- } else { value can be null (apparently in some cases)
TODO regexes?
foundmatch = foundmatch || ( pattern . test ( value . toLowerCase ())); TODO: early out (once we are true should break to spare unnecessary testing)
+ } else {
value can be null (apparently in some cases)
TODO regexes?
foundmatch = foundmatch || ( pattern . test ( value . toLowerCase ())); TODO: early out (once we are true should break to spare unnecessary testing)
if (foundmatch) return true;
});
- matches = matches && foundmatch ; TODO: early out (once false should break to spare unnecessary testing)
+ matches = matches && foundmatch ;
TODO: early out (once false should break to spare unnecessary testing)
if (!matches) return false;
});
return matches ;
});
@@ -155,9 +156,9 @@
if ( ! queryObj . facets ) {
return facetResults ;
}
- _ . each ( queryObj . facets , function ( query , facetId ) { TODO: remove dependency on recline.Model
facetResults [ facetId ] = new recline . Model . Facet ({ id : facetId }). toJSON ();
+ _ . each ( queryObj . facets , function ( query , facetId ) { TODO: remove dependency on recline.Model
facetResults [ facetId ] = new recline . Model . Facet ({ id : facetId }). toJSON ();
facetResults [ facetId ]. termsall = {};
- }); faceting
_ . each ( records , function ( doc ) {
+ }); faceting
_ . each ( records , function ( doc ) {
_ . each ( queryObj . facets , function ( query , facetId ) {
var fieldId = query . terms . field ;
var val = doc [ fieldId ];
@@ -174,21 +175,14 @@
var terms = _ . map ( tmp . termsall , function ( count , term ) {
return { term : term , count : count };
});
- tmp . terms = _ . sortBy ( terms , function ( item ) { want descending order
return - item . count ;
+ tmp . terms = _ . sortBy ( terms , function ( item ) { want descending order
return - item . count ;
});
tmp . terms = tmp . terms . slice ( 0 , 10 );
});
return facetResults ;
};
-
- this . transform = function ( editFunc ) {
- var toUpdate = recline . Data . Transform . mapDocs ( this . data , editFunc ); TODO: very inefficient -- could probably just walk the documents and updates in tandem and update
_ . each ( toUpdate . updates , function ( record , idx ) {
- self . data [ idx ] = record ;
- });
- return this . save ( toUpdate );
- };
};
-}( jQuery , this . recline . Backend . Memory ));
+}( this . recline . Backend . Memory ));
\ No newline at end of file
diff --git a/docs/src/ecma-fixes.html b/docs/src/ecma-fixes.html
index 212ecf5d5..3df0df090 100644
--- a/docs/src/ecma-fixes.html
+++ b/docs/src/ecma-fixes.html
@@ -1,4 +1,4 @@
- ecma-fixes.js
ecma-fixes.js This file adds in full array method support in browsers that don't support it
+
ecma-fixes.js
ecma-fixes.js 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
Add ECMA262-5 Array methods if not supported natively
if ( ! ( 'indexOf' in Array . prototype )) {
Array . prototype . indexOf = function ( find , i /*opt*/ ) {
if ( i === undefined ) i = 0 ;
diff --git a/docs/src/model.html b/docs/src/model.html
index a177a4604..80f8bc8f1 100644
--- a/docs/src/model.html
+++ b/docs/src/model.html
@@ -1,10 +1,10 @@
- model.js
model.js Recline Backbone Models this . recline = this . recline || {};
+ model.js
model.js Recline Backbone Models this . recline = this . recline || {};
this . recline . Model = this . recline . Model || {};
-( function ( $ , my ) { my . Dataset = Backbone . Model . extend ({
+( function ( my ) { use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; my . Dataset = Backbone . Model . extend ({
constructor : function Dataset () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
- }, initialize initialize : function () {
+ }, initialize initialize : function () {
_ . bindAll ( this , 'query' );
this . backend = null ;
if ( this . get ( 'backend' )) {
@@ -25,25 +25,25 @@
this . recordCount = null ;
this . queryState = new my . Query ();
this . queryState . bind ( 'change' , this . query );
- this . queryState . bind ( 'facet:add' , this . query ); store is what we query and save against
+ this . queryState . bind ( 'facet:add' , this . query );
store is what we query and save against
store will either be the backend or be a memory store if Backend fetch
tells us to use memory store
this . _store = this . backend ;
if ( this . backend == recline . Backend . Memory ) {
this . fetch ();
}
- }, fetch
+ }, fetch
Retrieve dataset and (some) records from the backend.
fetch : function () {
var self = this ;
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
if ( this . backend !== recline . Backend . Memory ) {
this . backend . fetch ( this . toJSON ())
. done ( handleResults )
- . fail ( function ( arguments ) {
- dfd . reject ( arguments );
+ . fail ( function ( args ) {
+ dfd . reject ( args );
});
- } else { special case where we have been given data directly
handleResults ({
+ } else { special case where we have been given data directly
handleResults ({
records : this . get ( 'records' ),
fields : this . get ( 'fields' ),
useMemoryStore : true
@@ -62,18 +62,18 @@
. done ( function () {
dfd . resolve ( self );
})
- . fail ( function ( arguments ) {
- dfd . reject ( arguments );
+ . fail ( function ( args ) {
+ dfd . reject ( args );
});
}
return dfd . promise ();
- }, _normalizeRecordsAndFields
+ }, _normalizeRecordsAndFields
Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects
e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] =>
-fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}]
_normalizeRecordsAndFields : function ( records , fields ) { if no fields get them from records
if ( ! fields && records && records . length > 0 ) { records is array then fields is first row of records ...
if ( records [ 0 ] instanceof Array ) {
+fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] _normalizeRecordsAndFields : function ( records , fields ) { if no fields get them from records
if ( ! fields && records && records . length > 0 ) { records is array then fields is first row of records ...
if ( records [ 0 ] instanceof Array ) {
fields = records [ 0 ];
records = records . slice ( 1 );
} else {
@@ -81,9 +81,14 @@
return { id : key };
});
}
- } fields is an array of strings (i.e. list of field headings/ids)
if ( fields && fields . length > 0 && typeof fields [ 0 ] === 'string' ) { Rename duplicate fieldIds as each field name needs to be
+ }
fields is an array of strings (i.e. list of field headings/ids)
if ( fields && fields . length > 0 && ( fields [ 0 ] === null || typeof ( fields [ 0 ]) != 'object' )) { Rename duplicate fieldIds as each field name needs to be
unique.
var seen = {};
- fields = _ . map ( fields , function ( field , index ) { cannot use trim as not supported by IE7
var fieldId = field . replace ( /^\s+|\s+$/g , '' );
+ fields = _ . map ( fields , function ( field , index ) {
+ if ( field === null ) {
+ field = '' ;
+ } else {
+ field = field . toString ();
+ } cannot use trim as not supported by IE7
var fieldId = field . replace ( /^\s+|\s+$/g , '' );
if ( fieldId === '' ) {
fieldId = '_noname_' ;
field = fieldId ;
@@ -94,10 +99,10 @@
}
if ( ! ( field in seen )) {
seen [ field ] = 0 ;
- } TODO: decide whether to keep original name as label ...
+ }
TODO: decide whether to keep original name as label ...
return { id: fieldId, label: field || fieldId }
return { id : fieldId };
});
- } records is provided as arrays so need to zip together with fields
+ }
records is provided as arrays so need to zip together with fields
NB: this requires you to have fields to match arrays
if ( records && records . length > 0 && records [ 0 ] instanceof Array ) {
records = _ . map ( records , function ( doc ) {
var tmp = {};
@@ -114,19 +119,7 @@
},
save : function () {
- var self = this ; TODO: need to reset the changes ...
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" });
- });
+ var self = this ; TODO: need to reset the changes ...
return this . _store . save ( this . _changes , this . toJSON ());
}, query
AJAX method with promise API to get records from the backend.
@@ -137,7 +130,7 @@
Resulting RecordList are used to reset this.records and are
also returned.
query : function ( queryObj ) {
var self = this ;
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
this . trigger ( 'query:start' );
if ( queryObj ) {
@@ -151,9 +144,9 @@
self . trigger ( 'query:done' );
dfd . resolve ( self . records );
})
- . fail ( function ( arguments ) {
- self . trigger ( 'query:fail' , arguments );
- dfd . reject ( arguments );
+ . fail ( function ( args ) {
+ self . trigger ( 'query:fail' , args );
+ dfd . reject ( args );
});
return dfd . promise ();
},
@@ -198,7 +191,7 @@
this . fields . each ( function ( field ) {
query . addFacet ( field . id );
});
- var dfd = $ . Deferred ();
+ var dfd = new Deferred ();
this . _store . query ( query . toJSON (), this . toJSON ()). done ( function ( queryResult ) {
if ( queryResult . facets ) {
_ . each ( queryResult . facets , function ( facetResult , facetId ) {
@@ -241,16 +234,23 @@
}, getFieldValue
For the provided Field get the corresponding rendered computed data value
-for this record.
getFieldValue : function ( field ) {
+for this record.
+
+NB: if field is undefined a default '' value will be returned
getFieldValue : function ( field ) {
val = this . getFieldValueUnrendered ( field );
- if ( field . renderer ) {
+ if ( field && ! _ . isUndefined ( field . renderer )) {
val = field . renderer ( val , field , this . toJSON ());
}
return val ;
}, getFieldValueUnrendered
For the provided Field get the corresponding computed data value
-for this record.
getFieldValueUnrendered : function ( field ) {
+for this record.
+
+NB: if field is undefined a default '' value will be returned
getFieldValueUnrendered : function ( field ) {
+ if ( ! field ) {
+ return '' ;
+ }
var val = this . get ( field . id );
if ( field . deriver ) {
val = field . deriver ( val , field , this );
@@ -350,7 +350,7 @@
here that are not actually strings if ( val && typeof val === 'string' ) {
val = val . replace ( /(https?:\/\/[^ ]+)/g , '<a href="$1">$1</a>' );
}
- return val
+ return val ;
}
}
}
@@ -398,7 +398,7 @@
Add a new filter specified by the filter hash and append to the list of filters
@param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates
addFilter : function ( filter ) { crude deep copy
var ourfilter = JSON . parse ( JSON . stringify ( filter )); not fully specified so use template and over-write
if ( _ . keys ( filter ). length <= 3 ) {
- ourfilter = _ . extend ( this . _filterTemplates [ filter . type ], ourfilter );
+ ourfilter = _ . defaults ( ourfilter , this . _filterTemplates [ filter . type ]);
}
var filters = this . get ( 'filters' );
filters . push ( ourfilter );
@@ -464,6 +464,6 @@
return model . backend . sync ( method , model , options );
};
-}( jQuery , this . recline . Model ));
+}( this . recline . Model ));
\ No newline at end of file
diff --git a/docs/src/view.flot.html b/docs/src/view.flot.html
new file mode 100644
index 000000000..1ed374bdb
--- /dev/null
+++ b/docs/src/view.flot.html
@@ -0,0 +1,452 @@
+ view.flot.js
view.flot.js /*jshint multistr:true */
+
+this . recline = this . recline || {};
+this . recline . View = this . recline . View || {};
+
+( function ( $ , my ) { Graph view for a Dataset using Flot 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 [flot options]}
+ }
+
+
+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 . Flot = Backbone . View . extend ({
+ template : ' \
+ <div class="recline-flot"> \
+ <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' , '_toolTip' , '_xaxisLabel' );
+ 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 . previousTooltipPoint = { x : null , y : null };
+ this . editor = new my . FlotControls ({
+ 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' );
+ this . $graph . on ( "plothover" , this . _toolTip );
+ return this ;
+ },
+
+ redraw : function () { There are 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 (( ! 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' )) {
+ var series = this . createSeries ();
+ var options = this . getGraphOptions ( this . state . attributes . graphType , series [ 0 ]. data . length );
+ this . plot = $ . plot ( this . $graph , series , options );
+ }
+ },
+
+ show : function () { because we cannot redraw when hidden we may need to when becoming visible
if ( this . needToRedraw ) {
+ this . redraw ();
+ }
+ }, infoboxes on mouse hover on points/bars etc
_toolTip : function ( event , pos , item ) {
+ if ( item ) {
+ if ( this . previousTooltipPoint . x !== item . dataIndex ||
+ this . previousTooltipPoint . y !== item . seriesIndex ) {
+ this . previousTooltipPoint . x = item . dataIndex ;
+ this . previousTooltipPoint . y = item . seriesIndex ;
+ $ ( "#recline-flot-tooltip" ). remove ();
+
+ var x = item . datapoint [ 0 ]. toFixed ( 2 ),
+ y = item . datapoint [ 1 ]. toFixed ( 2 );
+
+ if ( this . state . attributes . graphType === 'bars' ) {
+ x = item . datapoint [ 1 ]. toFixed ( 2 ),
+ y = item . datapoint [ 0 ]. toFixed ( 2 );
+ }
+
+ var content = _ . template ( '<%= group %> = <%= x %>, <%= series %> = <%= y %>' , {
+ group : this . state . attributes . group ,
+ x : this . _xaxisLabel ( x ),
+ series : item . series . label ,
+ y : y
+ }); use a different tooltip location offset for bar charts
var xLocation , yLocation ;
+ if ( this . state . attributes . graphType === 'bars' ) {
+ xLocation = item . pageX + 15 ;
+ yLocation = item . pageY - 10 ;
+ } else if ( this . state . attributes . graphType === 'columns' ) {
+ xLocation = item . pageX + 15 ;
+ yLocation = item . pageY ;
+ } else {
+ xLocation = item . pageX + 10 ;
+ yLocation = item . pageY - 20 ;
+ }
+
+ $ ( '<div id="recline-flot-tooltip">' + content + '</div>' ). css ({
+ top : yLocation ,
+ left : xLocation
+ }). appendTo ( "body" ). fadeIn ( 200 );
+ }
+ } else {
+ $ ( "#recline-flot-tooltip" ). remove ();
+ this . previousTooltipPoint . x = null ;
+ this . previousTooltipPoint . y = null ;
+ }
+ },
+
+ _xaxisLabel : function ( x ) {
+ var xfield = this . model . fields . get ( this . state . attributes . group ); time series
var xtype = xfield . get ( 'type' );
+ var isDateTime = ( xtype === 'date' || xtype === 'date-time' || xtype === 'time' );
+
+ if ( this . xvaluesAreIndex ) {
+ x = parseInt ( x , 10 ); HACK: deal with bar graph style cases where x-axis items were strings
+In this case x at this point is the index of the item in the list of
+records not its actual x-axis value
x = this . model . records . models [ x ]. get ( this . state . attributes . group );
+ }
+ if ( isDateTime ) {
+ x = new Date ( x ). toLocaleDateString ();
+ } } else if (isDateTime) {
+ x = new Date(parseInt(x, 10)).toLocaleDateString();
+}
getGraphOptions
+
+Get options for Flot Graph
+
+needs to be function as can depend on state
+
+@param typeId graphType id (lines, lines-and-points etc)
+@param numPoints the number of points that will be plotted
getGraphOptions : function ( typeId , numPoints ) {
+ var self = this ;
+
+ var tickFormatter = function ( x ) { convert x to a string and make sure that it is not too long or the
+tick labels will overlap
+TODO: find a more accurate way of calculating the size of tick labels
var label = self . _xaxisLabel ( x ) || "" ;
+
+ if ( typeof label !== 'string' ) {
+ label = label . toString ();
+ }
+ if ( self . state . attributes . graphType !== 'bars' && label . length > 10 ) {
+ label = label . slice ( 0 , 10 ) + "..." ;
+ }
+
+ return label ;
+ };
+
+ var xaxis = {};
+ xaxis . tickFormatter = tickFormatter ; for labels case we only want ticks at the label intervals
+HACK: however we also get this case with Date fields. In that case we
+could have a lot of values and so we limit to max 15 (we assume)
if ( this . xvaluesAreIndex ) {
+ var numTicks = Math . min ( this . model . records . length , 15 );
+ var increment = this . model . records . length / numTicks ;
+ var ticks = [];
+ for ( i = 0 ; i < numTicks ; i ++ ) {
+ ticks . push ( parseInt ( i * increment , 10 ));
+ }
+ xaxis . ticks = ticks ;
+ }
+
+ var yaxis = {};
+ yaxis . autoscale = true ;
+ yaxis . autoscaleMargin = 0.02 ;
+
+ var legend = {};
+ legend . position = 'ne' ;
+
+ var grid = {};
+ grid . hoverable = true ;
+ grid . clickable = true ;
+ grid . borderColor = "#aaaaaa" ;
+ grid . borderWidth = 1 ;
+
+ var optionsPerGraphType = {
+ lines : {
+ legend : legend ,
+ colors : this . graphColors ,
+ lines : { show : true },
+ xaxis : xaxis ,
+ yaxis : yaxis ,
+ grid : grid
+ },
+ points : {
+ legend : legend ,
+ colors : this . graphColors ,
+ points : { show : true , hitRadius : 5 },
+ xaxis : xaxis ,
+ yaxis : yaxis ,
+ grid : grid
+ },
+ 'lines-and-points' : {
+ legend : legend ,
+ colors : this . graphColors ,
+ points : { show : true , hitRadius : 5 },
+ lines : { show : true },
+ xaxis : xaxis ,
+ yaxis : yaxis ,
+ grid : grid
+ },
+ bars : {
+ legend : legend ,
+ colors : this . graphColors ,
+ lines : { show : false },
+ xaxis : yaxis ,
+ yaxis : xaxis ,
+ grid : grid ,
+ bars : {
+ show : true ,
+ horizontal : true ,
+ shadowSize : 0 ,
+ align : 'center' ,
+ barWidth : 0.8
+ }
+ },
+ columns : {
+ legend : legend ,
+ colors : this . graphColors ,
+ lines : { show : false },
+ xaxis : xaxis ,
+ yaxis : yaxis ,
+ grid : grid ,
+ bars : {
+ show : true ,
+ horizontal : false ,
+ shadowSize : 0 ,
+ align : 'center' ,
+ barWidth : 0.8
+ }
+ }
+ };
+
+ if ( self . state . get ( 'graphOptions' )) {
+ return _ . extend ( optionsPerGraphType [ typeId ],
+ self . state . get ( 'graphOptions' ));
+ } else {
+ return optionsPerGraphType [ typeId ];
+ }
+ },
+
+ createSeries : function () {
+ var self = this ;
+ self . xvaluesAreIndex = false ;
+ var series = [];
+ _ . each ( this . state . attributes . series , function ( field ) {
+ var points = [];
+ var fieldLabel = self . model . fields . get ( field ). get ( 'label' );
+ _ . 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 ) {
+ self . xvaluesAreIndex = true ;
+ x = index ;
+ } else if ( typeof x === 'string' ) {
+ x = parseFloat ( x );
+ if ( isNaN ( x )) { // assume this is a string label
+ x = index ;
+ self . xvaluesAreIndex = true ;
+ }
+ }
+
+ var yfield = self . model . fields . get ( field );
+ var y = doc . getFieldValue ( yfield );
+
+ if ( self . state . attributes . graphType == 'bars' ) {
+ points . push ([ y , x ]);
+ } else {
+ points . push ([ x , y ]);
+ }
+ });
+ series . push ({
+ data : points ,
+ label : fieldLabel ,
+ hoverable : true
+ });
+ });
+ return series ;
+ }
+});
+
+my . FlotControls = 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 );
+
+
\ No newline at end of file
diff --git a/docs/src/view.graph.html b/docs/src/view.graph.html
index fd87f132e..4088506ae 100644
--- a/docs/src/view.graph.html
+++ b/docs/src/view.graph.html
@@ -1,403 +1,6 @@
- view.graph.js
view.graph.js /*jshint multistr:true */
-
-this . recline = this . recline || {};
+ view.graph.js
view.graph.js this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
-
-( function ( $ , my ) { Graph view for a Dataset using Flot 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'
- }
-
-
-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 . Graph = 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 . GraphControls ({
- 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 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 (( ! 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 Flot 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 }
- };
- 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 . GraphControls = 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 (x-axis)</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}} (y-axis)</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 . View . Graph = this . recline . View . Flot ;
+this . recline . View . GraphControls = this . recline . View . FlotControls ;
\ No newline at end of file
diff --git a/docs/src/view.grid.html b/docs/src/view.grid.html
index 13efa9b74..fc0d31e49 100644
--- a/docs/src/view.grid.html
+++ b/docs/src/view.grid.html
@@ -1,4 +1,4 @@
- view.grid.js
view.grid.js /*jshint multistr:true */
+ view.grid.js
view.grid.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -85,8 +85,8 @@ Templating
});
this . scrollbarDimensions = this . scrollbarDimensions || this . _scrollbarSize (); // skip measurement if already have dimensions
var numFields = this . fields . length ; compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)
var fullWidth = self . el . width () - 20 - 10 * numFields - this . scrollbarDimensions . width ;
- var width = parseInt ( Math . max ( 50 , fullWidth / numFields )); if columns extend outside viewport then remainder is 0
var remainder = Math . max ( fullWidth - numFields * width , 0 );
- _ . each ( this . fields , function ( field , idx ) { add the remainder to the first field width so we make up full col
if ( idx == 0 ) {
+ var width = parseInt ( Math . max ( 50 , fullWidth / numFields ), 10 ); if columns extend outside viewport then remainder is 0
var remainder = Math . max ( fullWidth - numFields * width , 0 );
+ _ . each ( this . fields , function ( field , idx ) { add the remainder to the first field width so we make up full col
if ( idx === 0 ) {
field . set ({ width : width + remainder });
} else {
field . set ({ width : width });
diff --git a/docs/src/view.map.html b/docs/src/view.map.html
index d9f49d170..c37709b7f 100644
--- a/docs/src/view.map.html
+++ b/docs/src/view.map.html
@@ -1,4 +1,4 @@
- view.map.js
view.map.js /*jshint multistr:true */
+ view.map.js
view.map.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -6,9 +6,16 @@
( function ( $ , my ) { Map view for a Dataset using Leaflet mapping library.
This view allows to plot gereferenced records on a map. The location
-information can be provided either via a field with
-GeoJSON objects or two fields with latitude and
-longitude coordinates.
+information can be provided in 2 ways:
+
+
+Via a single field. This field must be either a geo_point or
+GeoJSON object
+Via two fields with latitude and longitude coordinates.
+
+
+Which fields in the data these correspond to can be configured via the state
+(and are guessed if no info is provided).
Initialization arguments are as standard for Dataset Views. State object may
have the following (optional) configuration options:
@@ -19,6 +26,9 @@
geomField: {id of field containing geometry in the dataset}
lonField: {id of field containing longitude in the dataset}
latField: {id of field containing latitude in the dataset}
+ autoZoom: true,
+ // use cluster support
+ cluster: false
}
@@ -103,7 +113,32 @@
}
}
return html ;
- }, END: Customization section Public: Adds the necessary elements to the page.
+ }, Options to use for the Leaflet GeoJSON layer
+See also http://leaflet.cloudmade.com/examples/geojson.html
+
+e.g.
+
+pointToLayer: function(feature, latLng)
+onEachFeature: function(feature, layer)
+
+
+See defaults for examples
pointToLayer function to use when creating points
+
+Default behaviour shown here is to create a marker using the
+popupContent set on the feature properties (created via infobox function
+during feature generation)
+
+NB: inside pointToLayer this
will be set to point to this map view
+instance (which allows e.g. this.markers to work in this default case)
pointToLayer : function ( feature , latlng ) {
+ var marker = new L . Marker ( latlng );
+ marker . bindPopup ( feature . properties . popupContent ); this is for cluster case
this . markers . addLayer ( marker );
+ return marker ;
+ }, onEachFeature default which adds popup in
onEachFeature : function ( feature , layer ) {
+ if ( feature . properties && feature . properties . popupContent ) {
+ layer . bindPopup ( feature . properties . popupContent );
+ }
+ }
+ }, END: Customization section Public: Adds the necessary elements to the page.
Also sets up the editor fields and the map if necessary.
render : function () {
var self = this ;
@@ -113,7 +148,7 @@
this . $map = this . el . find ( '.panel.map' );
this . redraw ();
return this ;
- }, Public: Redraws the features on the map according to the action provided
+ }, Public: Redraws the features on the map according to the action provided
Actions can be:
@@ -124,33 +159,39 @@
refresh: Clear existing features and add all current records
redraw : function ( action , doc ){
var self = this ;
- action = action || 'refresh' ; try to set things up if not already
if ( ! self . _geomReady ()){
+ action = action || 'refresh' ; try to set things up if not already
if ( ! self . _geomReady ()){
self . _setupGeometryField ();
}
if ( ! self . mapReady ){
self . _setupMap ();
}
- if ( this . _geomReady () && this . mapReady ){ removing ad re-adding the layer enables faster bulk loading
this . map . removeLayer ( this . features );
+ if ( this . _geomReady () && this . mapReady ){ removing ad re-adding the layer enables faster bulk loading
this . map . removeLayer ( this . features );
this . map . removeLayer ( this . markers );
var countBefore = 0 ;
this . features . eachLayer ( function (){ countBefore ++ ;});
if ( action == 'refresh' || action == 'reset' ) {
- this . features . clearLayers (); recreate cluster group because of issues with clearLayer
this . map . removeLayer ( this . markers );
+ this . features . clearLayers (); recreate cluster group because of issues with clearLayer
this . map . removeLayer ( this . markers );
this . markers = new L . MarkerClusterGroup ( this . _clusterOptions );
this . _add ( this . model . records . models );
} else if ( action == 'add' && doc ){
this . _add ( doc );
} else if ( action == 'remove' && doc ){
this . _remove ( doc );
- } enable clustering if there is a large number of markers
enable clustering if there is a large number of markers
var countAfter = 0 ;
this . features . eachLayer ( function (){ countAfter ++ ;});
var sizeIncreased = countAfter - countBefore > 0 ;
if ( ! this . state . get ( 'cluster' ) && countAfter > 64 && sizeIncreased ) {
this . state . set ({ cluster : true });
return ;
+ } this must come before zooming!
+if not: errors when using e.g. circle markers like
+"Cannot call method 'project' of undefined"
if ( this . state . get ( 'cluster' )) {
+ this . map . addLayer ( this . markers );
+ } else {
+ this . map . addLayer ( this . features );
}
if ( this . state . get ( 'autoZoom' )){
@@ -160,15 +201,10 @@
this . _zoomPending = true ;
}
}
- if ( this . state . get ( 'cluster' )) {
- this . map . addLayer ( this . markers );
- } else {
- this . map . addLayer ( this . features );
- }
}
},
- show : function () { If the div was hidden, Leaflet needs to recalculate some sizes
+ show : function () {
If the div was hidden, Leaflet needs to recalculate some sizes
to display properly
if ( this . map ){
this . map . invalidateSize ();
if ( this . _zoomPending && this . state . get ( 'autoZoom' )) {
@@ -185,7 +221,7 @@
_geomReady : function () {
return Boolean ( this . state . get ( 'geomField' ) || ( this . state . get ( 'latField' ) && this . state . get ( 'lonField' )));
- }, Private: Add one or n features to the map
+ }, Private: Add one or n features to the map
For each record passed, a GeoJSON geometry will be extracted and added
to the features layer. If an exception is thrown, the process will be
@@ -201,10 +237,10 @@
_ . every ( docs , function ( doc ){
count += 1 ;
var feature = self . _getGeometryFromRecord ( doc );
- if ( typeof feature === 'undefined' || feature === null ){
Empty field
return true ;
+ if ( typeof feature === 'undefined' || feature === null ){ Empty field
return true ;
} else if ( feature instanceof Object ){
feature . properties = {
- popupContent : self . infobox ( doc ), Add a reference to the model id, which will allow us to
+ popupContent : self . infobox ( doc ),
Add a reference to the model id, which will allow us to
link this Leaflet layer to a Recline doc
cid : doc . cid
};
@@ -226,7 +262,7 @@
}
return true ;
});
- }, Private: Remove one or n features from the map
_remove : function ( docs ){
+ }, Private: Remove one or n features from the map
_remove : function ( docs ){
var self = this ;
@@ -240,10 +276,10 @@
}
});
- }, Private: Return a GeoJSON geomtry extracted from the record fields
_getGeometryFromRecord : function ( doc ){
+ }, Private: Return a GeoJSON geomtry extracted from the record fields
_getGeometryFromRecord : function ( doc ){
if ( this . state . get ( 'geomField' )){
var value = doc . get ( this . state . get ( 'geomField' ));
- if ( typeof ( value ) === 'string' ){ We may have a GeoJSON string representation
try {
+ if ( typeof ( value ) === 'string' ){ We may have a GeoJSON string representation
try {
value = $ . parseJSON ( value );
} catch ( e ) {}
}
@@ -261,16 +297,16 @@
} else {
return null ;
}
- } else if ( value && value . slice ) { [ lon, lat ]
return {
+ } else if ( value && _ . isArray ( value )) { [ lon, lat ]
return {
"type" : "Point" ,
"coordinates" : [ value [ 0 ], value [ 1 ]]
};
- } else if ( value && value . lat ) { of form { lat: ..., lon: ...}
return {
+ } else if ( value && value . lat ) { of form { lat: ..., lon: ...}
return {
"type" : "Point" ,
"coordinates" : [ value . lon || value . lng , value . lat ]
};
- } We o/w assume that contents of the field are a valid GeoJSON object
return value ;
- } else if ( this . state . get ( 'lonField' ) && this . state . get ( 'latField' )){ We'll create a GeoJSON like point object from the two lat/lon fields
var lon = doc . get ( this . state . get ( 'lonField' ));
+ } We o/w assume that contents of the field are a valid GeoJSON object
return value ;
+ } else if ( this . state . get ( 'lonField' ) && this . state . get ( 'latField' )){ We'll create a GeoJSON like point object from the two lat/lon fields
var lon = doc . get ( this . state . get ( 'lonField' ));
var lat = doc . get ( this . state . get ( 'latField' ));
if ( ! isNaN ( parseFloat ( lon )) && ! isNaN ( parseFloat ( lat ))) {
return {
@@ -280,10 +316,10 @@
}
}
return null ;
- }, Private: Check if there is a field with GeoJSON geometries or alternatively,
+ },
Private: Check if there is a field with GeoJSON geometries or alternatively,
two fields with lat/lon values.
-If not found, the user can define them via the UI form.
_setupGeometryField : function (){ should not overwrite if we have already set this (e.g. explicitly via state)
if ( ! this . _geomReady ()) {
+If not found, the user can define them via the UI form.
_setupGeometryField : function (){ should not overwrite if we have already set this (e.g. explicitly via state)
if ( ! this . _geomReady ()) {
this . state . set ({
geomField : this . _checkField ( this . geometryFieldNames ),
latField : this . _checkField ( this . latitudeFieldNames ),
@@ -291,7 +327,7 @@
});
this . menu . state . set ( this . state . toJSON ());
}
- }, Private: Check if a field in the current model exists in the provided
+ },
Private: Check if a field in the current model exists in the provided
list of names.
_checkField : function ( fieldNames ){
var field ;
var modelFieldNames = this . model . fields . pluck ( 'id' );
@@ -302,7 +338,7 @@
}
}
return null ;
- }, Private: Zoom to map to current features extent if any, or to the full
+ },
Private: Zoom to map to current features extent if any, or to the full
extent if none.
_zoomToFeatures : function (){
var bounds = this . features . getBounds ();
if ( bounds && bounds . getNorthEast () && bounds . getSouthWest ()){
@@ -310,7 +346,7 @@
} else {
this . map . setView ([ 0 , 0 ], 2 );
}
- }, Private: Sets up the Leaflet map control and the features layer.
+ }, Private: Sets up the Leaflet map control and the features layer.
The map uses a base layer from MapQuest based
on OpenStreetMap .
_setupMap : function (){
@@ -322,21 +358,15 @@
var bg = new L . TileLayer ( mapUrl , { maxZoom : 18 , attribution : osmAttribution , subdomains : '1234' });
this . map . addLayer ( bg );
- this . markers = new L . MarkerClusterGroup ( this . _clusterOptions );
-
- this . features = new L . GeoJSON ( null ,{
- pointToLayer : function ( feature , latlng ) {
- var marker = new L . marker ( latlng );
- marker . bindPopup ( feature . properties . popupContent );
- self . markers . addLayer ( marker );
- return marker ;
- }
- });
+ this . markers = new L . MarkerClusterGroup ( this . _clusterOptions ); rebind this (as needed in e.g. default case above)
this . geoJsonLayerOptions . pointToLayer = _ . bind (
+ this . geoJsonLayerOptions . pointToLayer ,
+ this );
+ this . features = new L . GeoJSON ( null , this . geoJsonLayerOptions );
this . map . setView ([ 0 , 0 ], 2 );
this . mapReady = true ;
- }, Private: Helper function to select an option from a select list
_selectOption : function ( id , value ){
+ }, Private: Helper function to select an option from a select list
_selectOption : function ( id , value ){
var options = $ ( '.' + id + ' > select > option' );
if ( options ){
options . each ( function ( opt ){
@@ -409,7 +439,7 @@
<input type="hidden" class="editor-id" value="map-1" /> \
</div> \
</form> \
- ' , Define here events for UI elements
Define here events for UI elements
events : {
'click .editor-update-map' : 'onEditorSubmit' ,
'change .editor-field-type' : 'onFieldTypeChange' ,
'click #editor-auto-zoom' : 'onAutoZoomChange' ,
@@ -424,7 +454,7 @@
this . state = new recline . Model . ObjectState ( options . state );
this . state . bind ( 'change' , this . render );
this . render ();
- }, Public: Adds the necessary elements to the page.
+ }, Public: Adds the necessary elements to the page.
Also sets up the editor fields and the map if necessary.
render : function () {
var self = this ;
@@ -456,7 +486,7 @@
_geomReady : function () {
return Boolean ( this . state . get ( 'geomField' ) || ( this . state . get ( 'latField' ) && this . state . get ( 'lonField' )));
- }, UI Event handlers Public: Update map with user options
+ }, UI Event handlers Public: Update map with user options
Right now the only configurable option is what field(s) contains the
location information.
onEditorSubmit : function ( e ){
@@ -475,7 +505,7 @@
});
}
return false ;
- }, Public: Shows the relevant select lists depending on the location field
+ },
Public: Shows the relevant select lists depending on the location field
type selected.
onFieldTypeChange : function ( e ){
if ( e . target . value == 'geom' ){
this . el . find ( '.editor-field-type-geom' ). show ();
@@ -492,7 +522,7 @@
onClusteringChange : function ( e ){
this . state . set ({ cluster : ! this . state . get ( 'cluster' )});
- }, Private: Helper function to select an option from a select list
_selectOption : function ( id , value ){
+ }, 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 ){
diff --git a/docs/src/view.multiview.html b/docs/src/view.multiview.html
index d1f2f3837..0bba6c7f3 100644
--- a/docs/src/view.multiview.html
+++ b/docs/src/view.multiview.html
@@ -1,4 +1,4 @@
- view.multiview.js
view.multiview.js /*jshint multistr:true */ Standard JS module setup
this . recline = this . recline || {};
+ view.multiview.js
view.multiview.js /*jshint multistr:true */ Standard JS module setup
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
( function ( $ , my ) { MultiView
@@ -107,7 +107,7 @@ Parameters
<div class="menu-right"> \
<div class="btn-group" data-toggle="buttons-checkbox"> \
{{#sidebarViews}} \
- <a href="#" data-action="{{id}}" class="btn active">{{label}}</a> \
+ <a href="#" data-action="{{id}}" class="btn">{{label}}</a> \
{{/sidebarViews}} \
</div> \
</div> \
@@ -156,12 +156,6 @@ Parameters
model : this . model ,
state : this . state . get ( 'view-timeline' )
})
- }, {
- id : 'transform' ,
- label : 'Transform' ,
- view : new my . Transform ({
- model : this . model
- })
}];
} Hashes of sidebar elements
if ( options . sidebarViews ) {
this . sidebarViews = options . sidebarViews ;
@@ -189,6 +183,7 @@ Parameters
} else {
this . updateNav ( this . pageViews [ 0 ]. id );
}
+ this . _showHideSidebar ();
this . model . bind ( 'query:start' , function () {
self . notify ({ loader : true , persist : true });
@@ -255,20 +250,28 @@ Parameters
});
this . el . find ( '.query-editor-here' ). append ( queryEditor . el );
+ }, hide the sidebar if empty
_showHideSidebar : function () {
+ var $dataSidebar = this . el . find ( '.data-view-sidebar' );
+ var visibleChildren = $dataSidebar . children (). filter ( function () {
+ return $ ( this ). css ( "display" ) != "none" ;
+ }). length ;
+
+ if ( visibleChildren > 0 ) {
+ $dataSidebar . show ();
+ } else {
+ $dataSidebar . hide ();
+ }
},
updateNav : function ( pageName ) {
this . el . find ( '.navigation a' ). removeClass ( 'active' );
var $el = this . el . find ( '.navigation a[data-view="' + pageName + '"]' );
- $el . addClass ( 'active' ); show the specific page
_ . each ( this . pageViews , function ( view , idx ) {
+ $el . addClass ( 'active' ); add/remove sidebars and hide inactive views
_ . each ( this . pageViews , function ( view , idx ) {
if ( view . id === pageName ) {
view . view . el . show ();
if ( view . view . elSidebar ) {
view . view . elSidebar . show ();
}
- if ( view . view . show ) {
- view . view . show ();
- }
} else {
view . view . el . hide ();
if ( view . view . elSidebar ) {
@@ -279,12 +282,22 @@ Parameters
}
}
});
+
+ this . _showHideSidebar (); call view.view.show after sidebar visibility has been determined so
+that views can correctly calculate their maximum width
_ . each ( this . pageViews , function ( view , idx ) {
+ if ( view . id === pageName ) {
+ if ( view . view . show ) {
+ view . view . show ();
+ }
+ }
+ });
},
_onMenuClick : function ( e ) {
e . preventDefault ();
var action = $ ( e . target ). attr ( 'data-action' );
this [ '$' + action ]. toggle ();
+ this . _showHideSidebar ();
},
_onSwitchView : function ( e ) {
@@ -292,15 +305,15 @@ Parameters
var viewName = $ ( e . target ). attr ( 'data-view' );
this . updateNav ( viewName );
this . state . set ({ currentView : viewName });
- }, create a state object for this view and do the job of
+ }, create a state object for this view and do the job of
a) initializing it from both data passed in and other sources (e.g. hash url)
b) ensure the state object is updated in responese to changes in subviews, query etc.
_setupState : function ( initialState ) {
- var self = this ; get data from the query string / hash url plus some defaults
var qs = my . parseHashQueryString ();
+ var self = this ; get data from the query string / hash url plus some defaults
var qs = my . parseHashQueryString ();
var query = qs . reclineQuery ;
- query = query ? JSON . parse ( query ) : self . model . queryState . toJSON (); backwards compatability (now named view-graph but was named graph)
var graphState = qs [ 'view-graph' ] || qs . graph ;
- graphState = graphState ? JSON . parse ( graphState ) : {}; now get default data + hash url plus initial state and initial our state object with it
var stateData = _ . extend ({
+ query = query ? JSON . parse ( query ) : self . model . queryState . toJSON (); backwards compatability (now named view-graph but was named graph)
var graphState = qs [ 'view-graph' ] || qs . graph ;
+ graphState = graphState ? JSON . parse ( graphState ) : {}; now get default data + hash url plus initial state and initial our state object with it
var stateData = _ . extend ({
query : query ,
'view-graph' : graphState ,
backend : this . model . backend . __type__ ,
@@ -314,7 +327,7 @@ Parameters
},
_bindStateChanges : function () {
- var self = this ; finally ensure we update our state object when state of sub-object changes so that state is always up to date
this . model . queryState . bind ( 'change' , function () {
+ var self = this ; finally ensure we update our state object when state of sub-object changes so that state is always up to date
this . model . queryState . bind ( 'change' , function () {
self . state . set ({ query : self . model . queryState . toJSON ()});
});
_ . each ( this . pageViews , function ( pageView ) {
@@ -324,7 +337,7 @@ Parameters
self . state . set ( update );
pageView . view . state . bind ( 'change' , function () {
var update = {};
- update [ 'view-' + pageView . id ] = pageView . view . state . toJSON (); had problems where change not being triggered for e.g. grid view so let's do it explicitly
self . state . set ( update , { silent : true });
+ update [ 'view-' + pageView . id ] = pageView . view . state . toJSON (); had problems where change not being triggered for e.g. grid view so let's do it explicitly
self . state . set ( update , { silent : true });
self . state . trigger ( 'change' );
});
}
@@ -338,7 +351,7 @@ Parameters
self . notify ( flash );
});
});
- }, notify
+ }, notify
Create a notification (a div.alert in div.alert-messsages) using provided
flash object. Flash attributes (all are optional):
@@ -378,7 +391,7 @@ Parameters
});
}, 1000 );
}
- }, clearNotifications
+ }, clearNotifications
Clear all existing notifications
clearNotifications : function () {
var $notifications = $ ( '.recline-data-explorer .alert-messages .alert' );
@@ -386,17 +399,18 @@ Parameters
$ ( this ). remove ();
});
}
-}); MultiView.restore
+}); MultiView.restore
Restore a MultiView instance from a serialized state including the associated dataset
-This inverts the state serialization process in Multiview
my . MultiView . restore = function ( state ) { hack-y - restoring a memory dataset does not mean much ... (but useful for testing!)
if ( state . backend === 'memory' ) {
- var datasetInfo = {
+This inverts the state serialization process in Multiview
my . MultiView . restore = function ( state ) { hack-y - restoring a memory dataset does not mean much ... (but useful for testing!)
var datasetInfo ;
+ if ( state . backend === 'memory' ) {
+ datasetInfo = {
backend : 'memory' ,
records : [{ stub : 'this is a stub dataset because we do not restore memory datasets' }]
};
} else {
- var datasetInfo = _ . extend ({
+ datasetInfo = _ . extend ({
url : state . url ,
backend : state . backend
},
@@ -409,7 +423,7 @@ Parameters
state : state
});
return explorer ;
-} Miscellaneous Utilities var urlPathRegex = /^([^?]+)(\?.*)?/ ; Parse the Hash section of a URL into path and query string
my . parseHashUrl = function ( hashUrl ) {
+}; Miscellaneous Utilities var urlPathRegex = /^([^?]+)(\?.*)?/ ; Parse the Hash section of a URL into path and query string
my . parseHashUrl = function ( hashUrl ) {
var parsed = urlPathRegex . exec ( hashUrl );
if ( parsed === null ) {
return {};
@@ -419,7 +433,7 @@ Parameters
query : parsed [ 2 ] || ''
};
}
-}; Parse a URL query string (?xyz=abc...) into a dictionary.
my . parseQueryString = function ( q ) {
+}; Parse a URL query string (?xyz=abc...) into a dictionary.
my . parseQueryString = function ( q ) {
if ( ! q ) {
return {};
}
@@ -432,13 +446,13 @@ Parameters
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 ]);
+ 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 ;
-}; Parse the query string out of the URL hash
my . parseHashQueryString = function () {
+}; Parse the query string out of the URL hash
my . parseHashQueryString = function () {
q = my . parseHashUrl ( window . location . hash ). query ;
return my . parseQueryString ( q );
-}; Compse a Query String
my . composeQueryString = function ( queryParams ) {
+}; Compse a Query String
my . composeQueryString = function ( queryParams ) {
var queryString = '?' ;
var items = [];
$ . each ( queryParams , function ( key , value ) {
@@ -453,7 +467,7 @@ Parameters
my . getNewHashForQueryString = function ( queryParams ) {
var queryPart = my . composeQueryString ( queryParams );
- if ( window . location . hash ) { slice(1) to remove # at start
return window . location . hash . split ( '?' )[ 0 ]. slice ( 1 ) + queryPart ;
+ if ( window . location . hash ) { slice(1) to remove # at start
return window . location . hash . split ( '?' )[ 0 ]. slice ( 1 ) + queryPart ;
} else {
return queryPart ;
}
diff --git a/docs/src/view.slickgrid.html b/docs/src/view.slickgrid.html
index 481b5571d..08cab3a1d 100644
--- a/docs/src/view.slickgrid.html
+++ b/docs/src/view.slickgrid.html
@@ -1,4 +1,4 @@
- view.slickgrid.js
view.slickgrid.js /*jshint multistr:true */
+ view.slickgrid.js
view.slickgrid.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -11,7 +11,24 @@
Initialize it with a recline.Model.Dataset
.
-NB: you need an explicit height on the element for slickgrid to work
my . SlickGrid = Backbone . View . extend ({
+Additional options to drive SlickGrid grid can be given through state.
+The following keys allow for customization:
+* gridOptions: to add options at grid level
+* columnsEditor: to add editor for editable columns
+
+For example:
+ var grid = new recline.View.SlickGrid({
+ model: dataset,
+ el: $el,
+ state: {
+ gridOptions: {editable: true},
+ columnsEditor: [
+ {column: 'date', editor: Slick.Editors.Date },
+ {column: 'title', editor: Slick.Editors.Text}
+ ]
+ }
+ });
+// NB: you need an explicit height on the element for slickgrid to work
my . SlickGrid = Backbone . View . extend ({
initialize : function ( modelEtc ) {
var self = this ;
this . el = $ ( this . el );
@@ -20,14 +37,18 @@
this . model . records . bind ( 'add' , this . render );
this . model . records . bind ( 'reset' , this . render );
this . model . records . bind ( 'remove' , this . render );
+ this . model . records . bind ( 'change' , this . onRecordChanged , this );
var state = _ . extend ({
hiddenColumns : [],
columnsOrder : [],
columnsSort : {},
columnsWidth : [],
+ columnsEditor : [],
+ options : {},
fitColumns : false
}, modelEtc . state
+
);
this . state = new recline . Model . ObjectState ( state );
},
@@ -35,16 +56,24 @@
events : {
},
+ onRecordChanged : function ( record ) { Ignore if the grid is not yet drawn
if ( ! this . grid ) {
+ return ;
+ } Let's find the row corresponding to the index
var row_index = this . grid . getData (). getModelRow ( record );
+ this . grid . invalidateRow ( row_index );
+ this . grid . getData (). updateItem ( record , row_index );
+ this . grid . render ();
+ },
+
render : function () {
var self = this ;
- var options = {
+ var options = _ . extend ({
enableCellNavigation : true ,
enableColumnReorder : true ,
explicitInitialization : true ,
syncColumnCellResize : true ,
forceFitColumns : this . state . get ( 'fitColumns' )
- }; We need all columns, even the hidden ones, to show on the column picker
custom formatter as default one escapes html
+ }, self . state . get ( 'gridOptions' ));
We need all columns, even the hidden ones, to show on the column picker
custom formatter as default one escapes html
plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works ...)
row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values
var formatter = function ( row , cell , value , columnDef , dataContext ) {
var field = self . model . fields . get ( columnDef . id );
@@ -53,55 +82,81 @@
} else {
return value ;
}
- }
+ };
_ . each ( this . model . fields . toJSON (), function ( field ){
var column = {
- id : field [ 'id' ],
- name : field [ 'label' ],
- field : field [ 'id' ],
+ id : field . id ,
+ name : field . label ,
+ field : field . id ,
sortable : true ,
minWidth : 80 ,
formatter : formatter
};
- var widthInfo = _ . find ( self . state . get ( 'columnsWidth' ), function ( c ){ return c . column == field . id });
+ var widthInfo = _ . find ( self . state . get ( 'columnsWidth' ), function ( c ){ return c . column === field . id ;});
if ( widthInfo ){
- column [ 'width' ] = widthInfo . width ;
+ column . width = widthInfo . width ;
}
+ var editInfo = _ . find ( self . state . get ( 'columnsEditor' ), function ( c ){ return c . column === field . id ;});
+ if ( editInfo ){
+ column . editor = editInfo . editor ;
+ }
columns . push ( column );
- }); Restrict the visible columns
var visibleColumns = columns . filter ( function ( column ) {
- return _ . indexOf ( self . state . get ( 'hiddenColumns' ), column . id ) == - 1 ;
- }); Order them if there is ordering info on the state
if ( this . state . get ( 'columnsOrder' ) && this . state . get ( 'columnsOrder' ). length > 0 ) {
+ }); Restrict the visible columns
var visibleColumns = columns . filter ( function ( column ) {
+ return _ . indexOf ( self . state . get ( 'hiddenColumns' ), column . id ) === - 1 ;
+ }); Order them if there is ordering info on the state
if ( this . state . get ( 'columnsOrder' ) && this . state . get ( 'columnsOrder' ). length > 0 ) {
visibleColumns = visibleColumns . sort ( function ( a , b ){
return _ . indexOf ( self . state . get ( 'columnsOrder' ), a . id ) > _ . indexOf ( self . state . get ( 'columnsOrder' ), b . id ) ? 1 : - 1 ;
});
columns = columns . sort ( function ( a , b ){
return _ . indexOf ( self . state . get ( 'columnsOrder' ), a . id ) > _ . indexOf ( self . state . get ( 'columnsOrder' ), b . id ) ? 1 : - 1 ;
});
- } Move hidden columns to the end, so they appear at the bottom of the
+ }
Move hidden columns to the end, so they appear at the bottom of the
column picker
var tempHiddenColumns = [];
for ( var i = columns . length - 1 ; i >= 0 ; i -- ){
- if ( _ . indexOf ( _ . pluck ( visibleColumns , 'id' ), columns [ i ]. id ) == - 1 ){
+ if ( _ . indexOf ( _ . pluck ( visibleColumns , 'id' ), columns [ i ]. id ) === - 1 ){
tempHiddenColumns . push ( columns . splice ( i , 1 )[ 0 ]);
}
}
- columns = columns . concat ( tempHiddenColumns );
-
- var data = [];
-
- this . model . records . each ( function ( doc ){
+ columns = columns . concat ( tempHiddenColumns ); Transform a model object into a row
function toRow ( m ) {
var row = {};
self . model . fields . each ( function ( field ){
- row [ field . id ] = doc . getFieldValueUnrendered ( field );
+ row [ field . id ] = m . getFieldValueUnrendered ( field );
});
- data . push ( row );
+ return row ;
+ }
+
+ function RowSet () {
+ var models = [];
+ var rows = [];
+
+ this . push = function ( model , row ) {
+ models . push ( model );
+ rows . push ( row );
+ };
+
+ this . getLength = function () { return rows . length ; };
+ this . getItem = function ( index ) { return rows [ index ];};
+ this . getItemMetadata = function ( index ) { return {};};
+ this . getModel = function ( index ) { return models [ index ];};
+ this . getModelRow = function ( m ) { return models . indexOf ( m );};
+ this . updateItem = function ( m , i ) {
+ rows [ i ] = toRow ( m );
+ models [ i ] = m ;
+ };
+ }
+
+ var data = new RowSet ();
+
+ this . model . records . each ( function ( doc ){
+ data . push ( doc , toRow ( doc ));
});
- this . grid = new Slick . Grid ( this . el , data , visibleColumns , options ); Column sorting
var sortInfo = this . model . queryState . get ( 'sort' );
+ this . grid = new Slick . Grid ( this . el , data , visibleColumns , options ); Column sorting
var sortInfo = this . model . queryState . get ( 'sort' );
if ( sortInfo ){
var column = sortInfo [ 0 ]. field ;
- var sortAsc = ! ( sortInfo [ 0 ]. order == 'desc' );
+ var sortAsc = sortInfo [ 0 ]. order !== 'desc' ;
this . grid . setSortColumn ( column , sortAsc );
}
@@ -130,19 +185,27 @@
self . state . set ({ columnsWidth : columnsWidth });
});
+ this . grid . onCellChange . subscribe ( function ( e , args ) { We need to change the model associated value
var grid = args . grid ;
+ var model = data . getModel ( args . row );
+ var field = grid . getColumns ()[ args . cell ]. id ;
+ var v = {};
+ v [ field ] = args . item [ field ];
+ model . set ( v );
+ });
+
var columnpicker = new Slick . Controls . ColumnPicker ( columns , this . grid ,
_ . extend ( options ,{ state : this . state }));
if ( self . visible ){
self . grid . init ();
self . rendered = true ;
- } else { Defer rendering until the view is visible
self . rendered = false ;
+ } else { Defer rendering until the view is visible
self . rendered = false ;
}
return this ;
},
- show : function () { If the div is hidden, SlickGrid will calculate wrongly some
+ show : function () {
If the div is hidden, SlickGrid will calculate wrongly some
sizes so we must render it explicitly when the view is visible
if ( ! this . rendered ){
if ( ! this . grid ){
this . render ();
@@ -181,7 +244,7 @@
$menu = $ ( '<ul class="dropdown-menu slick-contextmenu" style="display:none;position:absolute;z-index:20;" />' ). appendTo ( document . body );
$menu . bind ( 'mouseleave' , function ( e ) {
- $ ( this ). fadeOut ( options . fadeSpeed )
+ $ ( this ). fadeOut ( options . fadeSpeed );
});
$menu . bind ( 'click' , updateColumn );
@@ -198,7 +261,7 @@
$input = $ ( '<input type="checkbox" />' ). data ( 'column-id' , columns [ i ]. id ). attr ( 'id' , 'slick-column-vis-' + columns [ i ]. id );
columnCheckboxes . push ( $input );
- if ( grid . getColumnIndex ( columns [ i ]. id ) != null ) {
+ if ( grid . getColumnIndex ( columns [ i ]. id ) !== null ) {
$input . attr ( 'checked' , 'checked' );
}
$input . appendTo ( $li );
@@ -225,10 +288,12 @@
}
function updateColumn ( e ) {
- if ( $ ( e . target ). data ( 'option' ) == 'autoresize' ) {
+ var checkbox ;
+
+ if ( $ ( e . target ). data ( 'option' ) === 'autoresize' ) {
var checked ;
if ( $ ( e . target ). is ( 'li' )){
- var checkbox = $ ( e . target ). find ( 'input' ). first ();
+ checkbox = $ ( e . target ). find ( 'input' ). first ();
checked = ! checkbox . is ( ':checked' );
checkbox . attr ( 'checked' , checked );
} else {
@@ -248,7 +313,7 @@
if (( $ ( e . target ). is ( 'li' ) && ! $ ( e . target ). hasClass ( 'divider' )) ||
$ ( e . target ). is ( 'input' )) {
if ( $ ( e . target ). is ( 'li' )){
- var checkbox = $ ( e . target ). find ( 'input' ). first ();
+ checkbox = $ ( e . target ). find ( 'input' ). first ();
checkbox . attr ( 'checked' , ! checkbox . is ( ':checked' ));
}
var visibleColumns = [];
@@ -261,7 +326,6 @@
}
});
-
if ( ! visibleColumns . length ) {
$ ( e . target ). attr ( 'checked' , 'checked' );
return ;
@@ -272,7 +336,7 @@
}
}
init ();
- } Slick.Controls.ColumnPicker
$ . extend ( true , window , { Slick : { Controls : { ColumnPicker : SlickColumnPicker }}});
+ } Slick.Controls.ColumnPicker
$ . extend ( true , window , { Slick : { Controls : { ColumnPicker : SlickColumnPicker }}});
})( jQuery );
\ No newline at end of file
diff --git a/docs/src/view.timeline.html b/docs/src/view.timeline.html
index cc25f5843..6f903f8bf 100644
--- a/docs/src/view.timeline.html
+++ b/docs/src/view.timeline.html
@@ -1,4 +1,4 @@
- view.timeline.js
view.timeline.js /*jshint multistr:true */
+ view.timeline.js
view.timeline.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -30,7 +30,8 @@
});
var stateData = _ . extend ({
startField : null ,
- endField : null
+ endField : null ,
+ timelineJSOptions : {}
},
options . state
);
@@ -53,13 +54,9 @@
},
_initTimeline : function () {
- var $timeline = this . el . find ( this . elementId ); set width explicitly o/w timeline goes wider that screen for some reason
var width = Math . max ( this . el . width (), this . el . find ( '.recline-timeline' ). width ());
- if ( width ) {
- $timeline . width ( width );
- }
- var config = {};
+ var $timeline = this . el . find ( this . elementId );
var data = this . _timelineJSON ();
- this . timeline . init ( data , this . elementId , config );
+ this . timeline . init ( data , this . elementId , this . state . get ( "timelineJSOptions" ));
this . _timelineIsInitialized = true
},
@@ -68,11 +65,11 @@
var data = this . _timelineJSON ();
this . timeline . reload ( data );
}
- }, Convert record to JSON for timeline
+ }, Convert record to JSON for timeline
Designed to be overridden in client apps
convertRecord : function ( record , fields ) {
return this . _convertRecord ( record , fields );
- }, Internal method to generate a Timeline formatted entry
_convertRecord : function ( record , fields ) {
+ }, Internal method to generate a Timeline formatted entry
_convertRecord : function ( record , fields ) {
var start = this . _parseDate ( record . get ( this . state . get ( 'startField' )));
var end = this . _parseDate ( record . get ( this . state . get ( 'endField' )));
if ( start ) {
@@ -103,7 +100,7 @@
if ( newEntry ) {
out . timeline . date . push ( newEntry );
}
- }); if no entries create a placeholder entry to prevent Timeline crashing with error
if ( out . timeline . date . length === 0 ) {
+ }); if no entries create a placeholder entry to prevent Timeline crashing with error
if ( out . timeline . date . length === 0 ) {
var tlEntry = {
"startDate" : '2000,1,1' ,
"headline" : 'No data to show!'
@@ -123,10 +120,7 @@
out = out . trim () ? moment ( out ) : null ;
if ( out . toDate () == 'Invalid Date' ) {
return null ;
- } else { fix for moment weirdness around date parsing and time zones
-moment('1914-08-01').toDate() => 1914-08-01 00:00 +01:00
-which in iso format (with 0 time offset) is 31 July 1914 23:00
-meanwhile native new Date('1914-08-01') => 1914-08-01 01:00 +01:00
out = out . subtract ( 'minutes' , out . zone ());
+ } else {
return out . toDate ();
}
},
diff --git a/docs/src/widget.facetviewer.html b/docs/src/widget.facetviewer.html
index 61ec2ff51..57ef47afc 100644
--- a/docs/src/widget.facetviewer.html
+++ b/docs/src/widget.facetviewer.html
@@ -1,4 +1,4 @@
- widget.facetviewer.js
widget.facetviewer.js /*jshint multistr:true */
+ widget.facetviewer.js
widget.facetviewer.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
diff --git a/docs/src/widget.fields.html b/docs/src/widget.fields.html
index 5d2b2cfd1..c98b3c4ba 100644
--- a/docs/src/widget.fields.html
+++ b/docs/src/widget.fields.html
@@ -1,4 +1,4 @@
- widget.fields.js
widget.fields.js /*jshint multistr:true */ Field Info
+ widget.fields.js
widget.fields.js /*jshint multistr:true */ Field Info
For each field
@@ -28,7 +28,7 @@
</small> \
</h4> \
</div> \
- <div id="collapse{{id}}" class="accordion-body collapse in"> \
+ <div id="collapse{{id}}" class="accordion-body collapse"> \
<div class="accordion-inner"> \
{{#facets}} \
<div class="facet-summary" data-facet="{{id}}"> \
@@ -47,9 +47,6 @@
</div> \
' ,
- events : {
- 'click .js-show-hide' : 'onShowHide'
- },
initialize : function ( model ) {
var self = this ;
this . el = $ ( this . el );
@@ -62,6 +59,7 @@
}); fields can get reset or changed in which case we need to recalculate
self . model . getFieldsSummary ();
self . render ();
});
+ this . el . find ( '.collapse' ). collapse ();
this . render ();
},
render : function () {
@@ -76,21 +74,6 @@
});
var templated = Mustache . render ( this . template , tmplData );
this . el . html ( templated );
- this . el . find ( '.collapse' ). collapse ( 'hide' );
- },
- onShowHide : function ( e ) {
- e . preventDefault ();
- var $target = $ ( e . target ); weird collapse class seems to have been removed (can watch this happen
-if you watch dom) but could not work why. Absence of collapse then meant
-we could not toggle.
-This seems to fix the problem.
this . el . find ( '.accordion-body' ). addClass ( 'collapse' );;
- if ( $target . text () === '+' ) {
- this . el . find ( '.collapse' ). collapse ( 'show' );
- $target . text ( '-' );
- } else {
- this . el . find ( '.collapse' ). collapse ( 'hide' );
- $target . text ( '+' );
- }
}
});
diff --git a/docs/src/widget.filtereditor.html b/docs/src/widget.filtereditor.html
index 128e953e9..4d0079b34 100644
--- a/docs/src/widget.filtereditor.html
+++ b/docs/src/widget.filtereditor.html
@@ -1,4 +1,4 @@
- widget.filtereditor.js
widget.filtereditor.js /*jshint multistr:true */
+ widget.filtereditor.js
widget.filtereditor.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -120,7 +120,7 @@
$target . hide ();
var filterType = $target . find ( 'select.filterType' ). val ();
var field = $target . find ( 'select.fields' ). val ();
- this . model . queryState . addFilter ({ type : filterType , field : field }); trigger render explicitly as queryState change will not be triggered (as blank value for filter)
this . render ();
+ this . model . queryState . addFilter ({ type : filterType , field : field });
},
onRemoveFilter : function ( e ) {
e . preventDefault ();
@@ -137,7 +137,7 @@
var $input = $ ( input );
var filterType = $input . attr ( 'data-filter-type' );
var fieldId = $input . attr ( 'data-filter-field' );
- var filterIndex = parseInt ( $input . attr ( 'data-filter-id' ));
+ var filterIndex = parseInt ( $input . attr ( 'data-filter-id' ), 10 );
var name = $input . attr ( 'name' );
var value = $input . val ();
@@ -158,7 +158,7 @@
break ;
}
});
- self . model . queryState . set ({ filters : filters });
+ self . model . queryState . set ({ filters : filters , from : 0 });
self . model . queryState . trigger ( 'change' );
}
});
diff --git a/docs/src/widget.pager.html b/docs/src/widget.pager.html
index 78a0925d8..5956b87e4 100644
--- a/docs/src/widget.pager.html
+++ b/docs/src/widget.pager.html
@@ -1,4 +1,4 @@
- widget.pager.js
widget.pager.js /*jshint multistr:true */
+ widget.pager.js
widget.pager.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
@@ -32,6 +32,8 @@
e . preventDefault ();
var newFrom = parseInt ( this . el . find ( 'input[name="from"]' ). val ());
var newSize = parseInt ( this . el . find ( 'input[name="to"]' ). val ()) - newFrom ;
+ newFrom = Math . max ( newFrom , 0 );
+ newSize = Math . max ( newSize , 1 );
this . model . set ({ size : newSize , from : newFrom });
},
onPaginationUpdate : function ( e ) {
@@ -43,6 +45,7 @@
} else {
newFrom = this . model . get ( 'from' ) + this . model . get ( 'size' );
}
+ newFrom = Math . max ( newFrom , 0 );
this . model . set ({ from : newFrom });
},
render : function () {
diff --git a/docs/src/widget.queryeditor.html b/docs/src/widget.queryeditor.html
index 38f6e2f38..0684637d7 100644
--- a/docs/src/widget.queryeditor.html
+++ b/docs/src/widget.queryeditor.html
@@ -1,4 +1,4 @@
- widget.queryeditor.js
widget.queryeditor.js /*jshint multistr:true */
+ widget.queryeditor.js
widget.queryeditor.js /*jshint multistr:true */
this . recline = this . recline || {};
this . recline . View = this . recline . View || {};