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)
( 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
+ "use strict" ;
+ my . __type__ = 'csv' ; use either jQuery or Underscore Deferred depending on what is available
var Deferred = ( typeof jQuery !== "undefined" && jQuery . Deferred ) || _ . Deferred ; fetch
fetch supports 3 options depending on the attribute provided on the dataset argument
@@ -25,36 +26,42 @@
var reader = new FileReader ();
var encoding = dataset . encoding || 'UTF-8' ;
reader . onload = function ( e ) {
- var rows = my . parseCSV ( e . target . result , dataset );
- dfd . resolve ({
- records : rows ,
- metadata : {
- filename : dataset . file . name
- },
- useMemoryStore : true
- });
+ var out = my . extractFields ( my . parseCSV ( e . target . result , dataset ), dataset );
+ out . useMemoryStore = true ;
+ out . metadata = {
+ filename : dataset . file . name
+ }
+ dfd . resolve ( out );
};
reader . onerror = function ( e ) {
alert ( 'Failed to load file. Code: ' + e . target . error . code );
};
reader . readAsText ( dataset . file , encoding );
} else if ( dataset . data ) {
- var rows = my . parseCSV ( dataset . data , dataset );
- dfd . resolve ({
- records : rows ,
- useMemoryStore : true
- });
+ var out = my . extractFields ( my . parseCSV ( dataset . data , dataset ), dataset );
+ out . useMemoryStore = true ;
+ dfd . resolve ( out );
} else if ( dataset . url ) {
jQuery . get ( dataset . url ). done ( function ( data ) {
- var rows = my . parseCSV ( data , dataset );
- dfd . resolve ({
- records : rows ,
- useMemoryStore : true
- });
+ var out = my . extractFields ( my . parseCSV ( data , dataset ), dataset );
+ out . useMemoryStore = true ;
+ dfd . resolve ( out );
});
}
return dfd . promise ();
- }; parseCSV
+ }; Convert array of rows in { records: [ ...] , fields: [ ... ] }
+@param {Boolean} noHeaderRow If true assume that first row is not a header (i.e. list of fields but is data.
my . extractFields = function ( rows , noFields ) {
+ if ( noFields . noHeaderRow !== true && rows . length > 0 ) {
+ return {
+ fields : rows [ 0 ],
+ records : rows . slice ( 1 )
+ }
+ } else {
+ return {
+ records : rows
+ }
+ }
+ }; parseCSV
Converts a Comma Separated Values string into an array of arrays.
Each line in the CSV becomes an array.
@@ -74,8 +81,10 @@
fields containing special characters, such as the delimiter or
quotechar, or which contain new-line characters. It defaults to '"'
+@param {Integer} skipInitialRows A integer number of rows to skip (default 0)
+
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 ;
@@ -92,10 +101,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 );
@@ -105,30 +114,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 );
+ out . push ( row ); Expose the ability to discard initial rows
if ( options . skipInitialRows ) out = out . slice ( options . skipInitialRows );
return out ;
- }; serializeCSV
+ }; serializeCSV
Convert an Object or a simple array of arrays into a Comma
Separated Values string.
@@ -180,9 +189,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 ;
@@ -192,12 +201,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 = '' ;
}
}
@@ -205,10 +214,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 ();
};
@@ -220,8 +229,8 @@
}());
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 );
}
}
diff --git a/docs/src/backend.dataproxy.html b/docs/src/backend.dataproxy.html
index d52d17db8..e3e685ba7 100644
--- a/docs/src/backend.dataproxy.html
+++ b/docs/src/backend.dataproxy.html
@@ -1,12 +1,13 @@
- 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 ) {
+ "use strict" ;
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
+ use either jQuery or Underscore Deferred depending on what is available
var Deferred = ( typeof jQuery !== "undefined" && jQuery . Deferred ) || _ . Deferred ; load
Load data from a URL via the DataProxy .
diff --git a/docs/src/backend.memory.html b/docs/src/backend.memory.html
index 5585712e3..10d4b52ec 100644
--- a/docs/src/backend.memory.html
+++ b/docs/src/backend.memory.html
@@ -1,9 +1,10 @@
- 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' ; private data - use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; Data Wrapper
+ "use strict" ;
+ my . __type__ = 'memory' ; private data - use either jQuery or Underscore Deferred depending on what is available
var Deferred = ( typeof jQuery !== "undefined" && jQuery . Deferred ) || _ . 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
@@ -82,6 +83,7 @@
};
in place filtering
this . _applyFilters = function ( results , queryObj ) {
var filters = queryObj . filters ; register filters
var filterFunctions = {
term : term ,
+ terms : terms ,
range : range ,
geo_distance : geo_distance
};
@@ -89,9 +91,9 @@
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 () }
+ string : function ( e ) { return e . toString (); },
+ date : function ( e ) { return moment ( e ). valueOf (); },
+ datetime : function ( e ) { return new Date ( e ). valueOf (); }
};
var keyedFields = {};
_ . each ( self . fields , function ( field ) {
@@ -112,17 +114,25 @@
return ( value === term );
}
+ function terms ( record , filter ) {
+ var parse = getDataParser ( filter );
+ var value = parse ( record [ filter . field ]);
+ var terms = parse ( filter . terms ). split ( "," );
+
+ return ( _ . indexOf ( terms , value ) >= 0 );
+ }
+
function range ( record , filter ) {
- var startnull = ( filter . start == null || filter . start === '' );
- var stopnull = ( filter . stop == null || filter . stop === '' );
+ var fromnull = ( _ . isUndefined ( filter . from ) || filter . from === null || filter . from === '' );
+ var tonull = ( _ . isUndefined ( filter . to ) || filter . to === null || filter . to === '' );
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
-note that for strings '' <= {any-character} e.g. '' <= 'a'
if (( ! startnull || ! stopnull ) && value === '' ) {
+ var from = parse ( fromnull ? '' : filter . from );
+ var to = parse ( tonull ? '' : filter . to ); 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 (( ! fromnull || ! tonull ) && value === '' ) {
return false ;
}
- return (( startnull || value >= start ) && ( stopnull || value <= stop ));
+ return (( fromnull || value >= from ) && ( tonull || value <= to ));
}
function geo_distance () { TODO code here
}
@@ -130,8 +140,8 @@
if ( queryObj . q ) {
var terms = queryObj . q . split ( ' ' );
var patterns = _ . map ( terms , function ( term ) {
- return new RegExp ( term . toLowerCase ());;
- });
+ return new RegExp ( term . toLowerCase ());
+ });
results = _ . filter ( results , function ( rawdoc ) {
var matches = true ;
_ . each ( patterns , function ( pattern ) {
diff --git a/docs/src/ecma-fixes.html b/docs/src/ecma-fixes.html
index 3df0df090..9f4b09dae 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 80f8bc8f1..3f318fe0b 100644
--- a/docs/src/model.html
+++ b/docs/src/model.html
@@ -1,10 +1,12 @@
- 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 ) { use either jQuery or Underscore Deferred depending on what is available
var Deferred = _ . isUndefined ( this . jQuery ) ? _ . Deferred : jQuery . Deferred ; my . Dataset = Backbone . Model . extend ({
+( function ( my ) {
+ "use strict" ; use either jQuery or Underscore Deferred depending on what is available
var Deferred = ( typeof jQuery !== "undefined" && jQuery . Deferred ) || _ . Deferred ; my . Dataset = Backbone . Model . extend ({
constructor : function Dataset () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
}, initialize initialize : function () {
+ var self = this ;
_ . bindAll ( this , 'query' );
this . backend = null ;
if ( this . get ( 'backend' )) {
@@ -24,14 +26,20 @@
this . facets = new my . FacetList ();
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 ( 'change facet:add' , function () {
+ self . query (); // We want to call query() without any arguments.
+ });
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 ;
+tells us to use memory store this . _store = this . backend ; if backend has a handleQueryResultFunction, use that
this . _handleResult = ( this . backend != null && _ . has ( this . backend , 'handleQueryResult' )) ?
+ this . backend . handleQueryResult : this . _handleQueryResult ;
if ( this . backend == recline . Backend . Memory ) {
this . fetch ();
}
- }, fetch
+ },
+
+ sync : function ( method , model , options ) {
+ return this . backend . sync ( method , model , options );
+ }, fetch
Retrieve dataset and (some) records from the backend.
fetch : function () {
var self = this ;
@@ -43,15 +51,19 @@
. 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
});
}
- function handleResults ( results ) {
- var out = self . _normalizeRecordsAndFields ( results . records , results . fields );
+ function handleResults ( results ) { if explicitly given the fields
+(e.g. var dataset = new Dataset({fields: fields, ...})
+use that field info over anything we get back by parsing the data
+(results.fields)
var fields = self . get ( 'fields' ) || results . fields ;
+
+ var out = self . _normalizeRecordsAndFields ( results . records , fields );
if ( results . useMemoryStore ) {
self . _store = new recline . Backend . Memory . Store ( out . records , out . fields );
}
@@ -68,12 +80,12 @@
}
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,14 +93,14 @@
return { id : key };
});
}
- } 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
+ }
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 ) {
if ( field === null ) {
field = '' ;
} else {
field = field . toString ();
- } cannot use trim as not supported by IE7
var fieldId = field . replace ( /^\s+|\s+$/g , '' );
+ } cannot use trim as not supported by IE7
var fieldId = field . replace ( /^\s+|\s+$/g , '' );
if ( fieldId === '' ) {
fieldId = '_noname_' ;
field = fieldId ;
@@ -99,10 +111,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 = {};
@@ -119,8 +131,8 @@
},
save : function () {
- var self = this ; TODO: need to reset the changes ...
return this . _store . save ( this . _changes , this . toJSON ());
- }, query
+ 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.
@@ -140,7 +152,7 @@
this . _store . query ( actualQuery , this . toJSON ())
. done ( function ( queryResult ) {
- self . _handleQueryResult ( queryResult );
+ self . _handleResult ( queryResult );
self . trigger ( 'query:done' );
dfd . resolve ( self . records );
})
@@ -180,7 +192,7 @@
data . recordCount = this . recordCount ;
data . fields = this . fields . toJSON ();
return data ;
- }, getFieldsSummary
+ }, getFieldsSummary
Get a summary for each field in the form of a Facet
.
@@ -196,15 +208,15 @@
if ( queryResult . facets ) {
_ . each ( queryResult . facets , function ( facetResult , facetId ) {
facetResult . id = facetId ;
- var facet = new my . Facet ( facetResult ); TODO: probably want replace rather than reset (i.e. just replace the facet with this id)
self . fields . get ( facetId ). facets . reset ( facet );
+ var facet = new my . Facet ( facetResult ); TODO: probably want replace rather than reset (i.e. just replace the facet with this id)
self . fields . get ( facetId ). facets . reset ( facet );
});
}
dfd . resolve ( queryResult );
});
return dfd . promise ();
- }, Deprecated (as of v0.5) - use record.summary()
recordSummary : function ( record ) {
+ }, Deprecated (as of v0.5) - use record.summary()
recordSummary : function ( record ) {
return record . summary ();
- }, _backendFromString(backendString)
+ }, _backendFromString(backendString)
Look up a backend module from a backend string (look in recline.Backend)
_backendFromString : function ( backendString ) {
var backend = null ;
@@ -217,12 +229,12 @@
}
return backend ;
}
-});
+});
A single record (or row) in the dataset
my . Record = Backbone . Model . extend ({
constructor : function Record () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
- }, initialize
+ }, initialize
Create a Record
@@ -231,18 +243,18 @@
Certain methods require presence of a fields attribute (identical to that on Dataset)
initialize : function () {
_ . bindAll ( this , 'getFieldValue' );
- }, getFieldValue
+ }, getFieldValue
For the provided Field get the corresponding rendered computed data value
for this record.
NB: if field is undefined a default '' value will be returned
getFieldValue : function ( field ) {
- val = this . getFieldValueUnrendered ( field );
+ var val = this . getFieldValueUnrendered ( field );
if ( field && ! _ . isUndefined ( field . renderer )) {
val = field . renderer ( val , field , this . toJSON ());
}
return val ;
- }, getFieldValueUnrendered
+ }, getFieldValueUnrendered
For the provided Field get the corresponding computed data value
for this record.
@@ -256,7 +268,7 @@
val = field . deriver ( val , field , this );
}
return val ;
- }, summary
+ }, summary
Get a simple html summary of this record in form of key/value list
summary : function ( record ) {
var self = this ;
@@ -268,30 +280,30 @@
});
html += '</div>' ;
return html ;
- }, Override Backbone save, fetch and destroy so they do nothing
+ },
Override Backbone save, fetch and destroy so they do nothing
Instead, Dataset object that created this Record should take care of
handling these changes (discovery will occur via event notifications)
WARNING: these will not persist unless you call save on Dataset
fetch : function () {},
save : function () {},
destroy : function () { this . trigger ( 'destroy' , this ); }
-}); A Backbone collection of Records my . RecordList = Backbone . Collection . extend ({
+}); A Backbone collection of Records my . RecordList = Backbone . Collection . extend ({
constructor : function RecordList () {
Backbone . Collection . prototype . constructor . apply ( this , arguments );
},
model : my . Record
-}); my . Field = Backbone . Model . extend ({
+}); my . Field = Backbone . Model . extend ({
constructor : function Field () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
- }, defaults - define default values defaults - define default values defaults : {
label : null ,
type : 'string' ,
format : null ,
is_derived : false
- }, initialize
+ }, initialize
@param {Object} data: standard Backbone model attributes
-@param {Object} options: renderer and/or deriver functions.
initialize : function ( data , options ) { if a hash not passed in the first argument throw error
if ( '0' in data ) {
+@param {Object} options: renderer and/or deriver functions.
initialize : function ( data , options ) { if a hash not passed in the first argument throw error
if ( '0' in data ) {
throw new Error ( 'Looks like you did not pass a proper hash with id to Field constructor' );
}
if ( this . attributes . label === null ) {
@@ -346,7 +358,7 @@
}
} else if ( format == 'plain' ) {
return val ;
- } else { as this is the default and default type is string may get things
+ } else {
as this is the default and default type is string may get things
here that are not actually strings
if ( val && typeof val === 'string' ) {
val = val . replace ( /(https?:\/\/[^ ]+)/g , '<a href="$1">$1</a>' );
}
@@ -361,7 +373,7 @@
Backbone . Collection . prototype . constructor . apply ( this , arguments );
},
model : my . Field
-}); my . Query = Backbone . Model . extend ({
+}); my . Query = Backbone . Model . extend ({
constructor : function Query () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
},
@@ -376,13 +388,13 @@
},
_filterTemplates : {
term : {
- type : 'term' , TODO do we need this attribute here?
field : '' ,
+ type : 'term' , TODO do we need this attribute here?
field : '' ,
term : ''
},
range : {
type : 'range' ,
- start : '' ,
- stop : ''
+ from : '' ,
+ to : ''
},
geo_distance : {
type : 'geo_distance' ,
@@ -393,38 +405,56 @@
lat : 0
}
}
- }, addFilter(filter)
+ }, addFilter(filter)
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 ) {
+@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 = _ . defaults ( ourfilter , this . _filterTemplates [ filter . type ]);
}
var filters = this . get ( 'filters' );
filters . push ( ourfilter );
this . trigger ( 'change:filters:new-blank' );
},
+ replaceFilter : function ( filter ) { delete filter on the same field, then add
var filters = this . get ( 'filters' );
+ var idx = - 1 ;
+ _ . each ( this . get ( 'filters' ), function ( f , key , list ) {
+ if ( filter . field == f . field ) {
+ idx = key ;
+ }
+ }); trigger just one event (change:filters:new-blank) instead of one for remove and
+one for add
if ( idx >= 0 ) {
+ filters . splice ( idx , 1 );
+ this . set ({ filters : filters });
+ }
+ this . addFilter ( filter );
+ },
updateFilter : function ( index , value ) {
- }, removeFilter
+ }, removeFilter
Remove a filter from filters at index filterIndex
removeFilter : function ( filterIndex ) {
var filters = this . get ( 'filters' );
filters . splice ( filterIndex , 1 );
this . set ({ filters : filters });
this . trigger ( 'change' );
- }, addFacet
+ }, addFacet
Add a Facet to this query
-See http://www.elasticsearch.org/guide/reference/api/search/facets/
addFacet : function ( fieldId ) {
- var facets = this . get ( 'facets' ); Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field)
addFacet : function ( fieldId , size , silent ) {
+ var facets = this . get ( 'facets' ); Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field)
if ( _ . contains ( _ . keys ( facets ), fieldId )) {
return ;
}
facets [ fieldId ] = {
terms : { field : fieldId }
};
+ if ( ! _ . isUndefined ( size )) {
+ facets [ fieldId ]. terms . size = size ;
+ }
this . set ({ facets : facets }, { silent : true });
- this . trigger ( 'facet:add' , this );
+ if ( ! silent ) {
+ this . trigger ( 'facet:add' , this );
+ }
},
addHistogramFacet : function ( fieldId ) {
var facets = this . get ( 'facets' );
@@ -436,8 +466,27 @@
};
this . set ({ facets : facets }, { silent : true });
this . trigger ( 'facet:add' , this );
+ },
+ removeFacet : function ( fieldId ) {
+ var facets = this . get ( 'facets' ); Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field)
if ( ! _ . contains ( _ . keys ( facets ), fieldId )) {
+ return ;
+ }
+ delete facets [ fieldId ];
+ this . set ({ facets : facets }, { silent : true });
+ this . trigger ( 'facet:remove' , this );
+ },
+ clearFacets : function () {
+ var facets = this . get ( 'facets' );
+ _ . each ( _ . keys ( facets ), function ( fieldId ) {
+ delete facets [ fieldId ];
+ });
+ this . trigger ( 'facet:remove' , this );
+ }, trigger a facet add; use this to trigger a single event after adding
+multiple facets
refreshFacets : function () {
+ this . trigger ( 'facet:add' , this );
}
-}); my . Facet = Backbone . Model . extend ({
+
+}); my . Facet = Backbone . Model . extend ({
constructor : function Facet () {
Backbone . Model . prototype . constructor . apply ( this , arguments );
},
@@ -450,20 +499,19 @@
terms : []
};
}
-}); A Collection/List of Facets my . FacetList = Backbone . Collection . extend ({
+}); A Collection/List of Facets my . FacetList = Backbone . Collection . extend ({
constructor : function FacetList () {
Backbone . Collection . prototype . constructor . apply ( this , arguments );
},
model : my . Facet
-}); Object State
+}); Object State
Convenience Backbone model for storing (configuration) state of objects like Views.
my . ObjectState = Backbone . Model . extend ({
-}); Backbone.sync
-
-Override Backbone.sync to hand off to sync function in relevant backend
Backbone . sync = function ( method , model , options ) {
- return model . backend . sync ( method , model , options );
-};
+}); Backbone.sync
-}( this . recline . Model ));
+Override Backbone.sync to hand off to sync function in relevant backend
+Backbone.sync = function(method, model, options) {
+ return model.backend.sync(method, model, options);
+};
\ No newline at end of file
diff --git a/docs/src/view.flot.html b/docs/src/view.flot.html
index 1ed374bdb..08a8a0303 100644
--- a/docs/src/view.flot.html
+++ b/docs/src/view.flot.html
@@ -1,9 +1,10 @@
- view.flot.js
view.flot.js /*jshint multistr:true */
+ 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.
+( function ( $ , my ) {
+ "use strict" ; Graph view for a Dataset using Flot graphing library.
Initialization arguments (in a hash in first parameter):
@@ -14,7 +15,8 @@
{
group: {column name for x-axis},
series: [{column name for series A}, {column name series B}, ... ],
- graphType: 'line',
+ // options are: lines, points, lines-and-points, bars, columns
+ graphType: 'lines',
graphOptions: {custom [flot options]}
}
@@ -37,14 +39,11 @@
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 );
+ this . listenTo ( this . model , 'change' , this . render );
+ this . listenTo ( this . model . fields , 'reset add' , this . render );
+ this . listenTo ( this . model . records , 'reset add' , this . redraw );
var stateData = _ . extend ({
group : null , so that at least one series chooser box shows up
series : [],
graphType : 'lines-and-points'
@@ -57,27 +56,32 @@
model : this . model ,
state : this . state . toJSON ()
});
- this . editor . state . bind ( 'change' , function () {
+ this . listenTo ( this . editor . state , 'change' , function () {
self . state . set ( self . editor . state . toJSON ());
self . redraw ();
});
- this . elSidebar = this . editor . el ;
+ 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 . $el . html ( htmls );
+ this . $graph = this . $el . find ( '.panel.graph' );
this . $graph . on ( "plothover" , this . _toolTip );
return this ;
},
+ remove : function () {
+ this . editor . remove ();
+ Backbone . View . prototype . remove . apply ( this , arguments );
+ },
+
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 ]);
+* 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 );
if (( ! areWeVisible || this . model . records . length === 0 )) {
this . needToRedraw = true ;
return ;
@@ -137,20 +141,16 @@
},
_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 ) {
+ if ( this . _groupFieldIsDateTime ()) { oddly x comes through as milliseconds string (rather than int
+or float) so we have to reparse
x = new Date ( parseFloat ( x )). toLocaleDateString ();
+ } else 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
+
+ return x ;
+ }, getGraphOptions
Get options for Flot Graph
@@ -159,32 +159,35 @@
@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 groupFieldIsDateTime = self . _groupFieldIsDateTime ();
+ var xaxis = {};
- var tickFormatter = function ( x ) { convert x to a string and make sure that it is not too long or the
+ if ( ! groupFieldIsDateTime ) {
+ xaxis . 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 ) || "" ;
+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 ;
- };
+ if ( typeof label !== 'string' ) {
+ label = label . toString ();
+ }
+ if ( self . state . attributes . graphType !== 'bars' && label . length > 10 ) {
+ label = label . slice ( 0 , 10 ) + "..." ;
+ }
- var xaxis = {};
- xaxis . tickFormatter = tickFormatter ; for labels case we only want ticks at the label intervals
+ return label ;
+ };
+ }
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 ++ ) {
+ for ( var i = 0 ; i < numTicks ; i ++ ) {
ticks . push ( parseInt ( i * increment , 10 ));
}
xaxis . ticks = ticks ;
+ } else if ( groupFieldIsDateTime ) {
+ xaxis . mode = 'time' ;
}
var yaxis = {};
@@ -266,31 +269,54 @@
}
},
+ _groupFieldIsDateTime : function () {
+ var xfield = this . model . fields . get ( this . state . attributes . group );
+ var xtype = xfield . get ( 'type' );
+ var isDateTime = ( xtype === 'date' || xtype === 'date-time' || xtype === 'time' );
+ return isDateTime ;
+ },
+
createSeries : function () {
var self = this ;
self . xvaluesAreIndex = false ;
var series = [];
+ var xfield = self . model . fields . get ( self . state . attributes . group );
+ var isDateTime = self . _groupFieldIsDateTime ();
+
_ . each ( this . state . attributes . series , function ( field ) {
var points = [];
var fieldLabel = self . model . fields . get ( field ). get ( 'label' );
+
+ if ( isDateTime ){
+ var cast = function ( x ){
+ var _date = moment ( String ( x ));
+ if ( _date . isValid ()) {
+ x = _date . toDate (). getTime ();
+ }
+ return x
+ }
+ } else {
+ var raw = _ . map ( self . model . records . models ,
+ function ( doc , index ){
+ return doc . getFieldValueUnrendered ( xfield )
+ });
+
+ if ( _ . all ( raw , function ( x ){ return ! isNaN ( parseFloat ( x )) })){
+ var cast = function ( x ){ return parseFloat ( x ) }
+ } else {
+ self . xvaluesAreIndex = true
+ }
+ }
+
_ . 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 ;
- }
+ if ( self . xvaluesAreIndex ){
+ var x = index ;
+ } else {
+ var x = cast ( doc . getFieldValueUnrendered ( xfield ));
}
var yfield = self . model . fields . get ( field );
- var y = doc . getFieldValue ( yfield );
+ var y = doc . getFieldValueUnrendered ( yfield );
if ( self . state . attributes . graphType == 'bars' ) {
points . push ([ y , x ]);
@@ -368,10 +394,8 @@
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 . listenTo ( this . model . fields , 'reset add' , this . render );
this . state = new recline . Model . ObjectState ( options . state );
this . render ();
},
@@ -380,12 +404,12 @@
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 . $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 = [ "" ];
+ } ensure at least one series box shows up
var tmpSeries = [ "" ];
if ( this . state . get ( 'series' ). length > 0 ) {
tmpSeries = this . state . get ( 'series' );
}
@@ -394,8 +418,8 @@
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' );
+ }, 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 ) {
@@ -407,19 +431,19 @@
},
onEditorSubmit : function ( e ) {
- var select = this . el . find ( '.editor-group select' );
+ var select = this . $el . find ( '.editor-group select' );
var $editor = this ;
- var $series = this . el . find ( '.editor-series select' );
+ 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 ()
+ 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.
+ }, Public: Adds a new empty series select box to the editor.
@param [int] idx index of this series in the list of series
@@ -430,14 +454,14 @@
}, this . model . toTemplateJSON ());
var htmls = Mustache . render ( this . templateSeriesEditor , data );
- this . el . find ( '.editor-series-group' ). append ( htmls );
+ 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.
+ }, Public: Removes a series list item from the editor.
Also updates the labels of the remaining series elements.
removeSeries : function ( e ) {
e . preventDefault ();
diff --git a/docs/src/view.graph.html b/docs/src/view.graph.html
index 4088506ae..bf5b23a5d 100644
--- a/docs/src/view.graph.html
+++ b/docs/src/view.graph.html
@@ -1,4 +1,4 @@
- view.graph.js
view.graph.js this . recline = this . recline || {};
+ view.graph.js
view.graph.js this . recline = this . recline || {};
this . recline . View = this . recline . View || {};
this . recline . View . Graph = this . recline . View . Flot ;
this . recline . View . GraphControls = this . recline . View . FlotControls ;
diff --git a/docs/src/view.grid.html b/docs/src/view.grid.html
index fc0d31e49..7f1fc32d5 100644
--- a/docs/src/view.grid.html
+++ b/docs/src/view.grid.html
@@ -1,9 +1,10 @@
- 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 || {};
-( function ( $ , my ) { (Data) Grid Dataset View
+( function ( $ , my ) {
+ "use strict" ; (Data) Grid Dataset View
Provides a tabular view on a Dataset.
@@ -13,11 +14,8 @@
initialize : function ( modelEtc ) {
var self = this ;
- this . el = $ ( this . el );
_ . bindAll ( this , 'render' , 'onHorizontalScroll' );
- this . model . records . bind ( 'add' , this . render );
- this . model . records . bind ( 'reset' , this . render );
- this . model . records . bind ( 'remove' , this . render );
+ this . listenTo ( this . model . records , 'add reset remove' , this . render );
this . tempState = {};
var state = _ . extend ({
hiddenFields : []
@@ -49,7 +47,7 @@
onHorizontalScroll : function ( e ) {
var currentScroll = $ ( e . target ). scrollLeft ();
- this . el . find ( '.recline-grid thead tr' ). scrollLeft ( currentScroll );
+ this . $el . find ( '.recline-grid thead tr' ). scrollLeft ( currentScroll );
}, ======================================================
Templating template : ' \
@@ -73,42 +71,43 @@ Templating
toTemplateJSON : function () {
var self = this ;
var modelData = this . model . toJSON ();
- modelData . notEmpty = ( this . fields . length > 0 ); TODO: move this sort of thing into a toTemplateJSON method on Dataset?
modelData . fields = _ . map ( this . fields , function ( field ) {
+ modelData . notEmpty = ( this . fields . length > 0 ); TODO: move this sort of thing into a toTemplateJSON method on Dataset?
modelData . fields = this . fields . map ( function ( field ) {
return field . toJSON ();
}); last header width = scroll bar - border (2px) */
modelData . lastHeaderWidth = this . scrollbarDimensions . width - 2 ;
return modelData ;
},
render : function () {
var self = this ;
- this . fields = this . model . fields . filter ( function ( field ) {
+ this . fields = new recline . Model . FieldList ( this . model . fields . filter ( function ( field ) {
return _ . indexOf ( self . state . get ( 'hiddenFields' ), field . id ) == - 1 ;
- });
+ }));
+
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 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 ), 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 ) {
+ this . fields . each ( 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 });
}
});
var htmls = Mustache . render ( this . template , this . toTemplateJSON ());
- this . el . html ( htmls );
+ this . $el . html ( htmls );
this . model . records . forEach ( function ( doc ) {
var tr = $ ( '<tr />' );
- self . el . find ( 'tbody' ). append ( tr );
+ self . $el . find ( 'tbody' ). append ( tr );
var newView = new my . GridRow ({
model : doc ,
el : tr ,
fields : self . fields
});
newView . render ();
- }); hide extra header col if no scrollbar to avoid unsightly overhang
var $tbody = this . el . find ( 'tbody' )[ 0 ];
+ }); hide extra header col if no scrollbar to avoid unsightly overhang
var $tbody = this . $el . find ( 'tbody' )[ 0 ];
if ( $tbody . scrollHeight <= $tbody . offsetHeight ) {
- this . el . find ( 'th.last-header' ). hide ();
+ this . $el . find ( 'th.last-header' ). hide ();
}
- this . el . find ( '.recline-grid' ). toggleClass ( 'no-hidden' , ( self . state . get ( 'hiddenFields' ). length === 0 ));
- this . el . find ( '.recline-grid tbody' ). scroll ( this . onHorizontalScroll );
+ this . $el . find ( '.recline-grid' ). toggleClass ( 'no-hidden' , ( self . state . get ( 'hiddenFields' ). length === 0 ));
+ this . $el . find ( '.recline-grid tbody' ). scroll ( this . onHorizontalScroll );
return this ;
}, _scrollbarSize
@@ -138,8 +137,7 @@ Templating
initialize : function ( initData ) {
_ . bindAll ( this , 'render' );
this . _fields = initData . fields ;
- this . el = $ ( this . el );
- this . model . bind ( 'change' , this . render );
+ this . listenTo ( this . model , 'change' , this . render );
},
template : ' \
@@ -172,9 +170,9 @@ Templating
},
render : function () {
- this . el . attr ( 'data-id' , this . model . id );
+ this . $el . attr ( 'data-id' , this . model . id );
var html = Mustache . render ( this . template , this . toTemplateJSON ());
- $ ( this . el ). html ( html );
+ this . $el . html ( html );
return this ;
}, ===================
Cell Editor methods
cellEditorTemplate : ' \
@@ -190,7 +188,7 @@ Templating
' ,
onEditClick : function ( e ) {
- var editing = this . el . find ( '.data-table-cell-editor-editor' );
+ var editing = this . $el . find ( '.data-table-cell-editor-editor' );
if ( editing . length > 0 ) {
editing . parents ( '.data-table-cell-value' ). html ( editing . text ()). siblings ( '.data-table-cell-edit' ). removeClass ( "hidden" );
}
diff --git a/docs/src/view.map.html b/docs/src/view.map.html
index c37709b7f..d5115b00d 100644
--- a/docs/src/view.map.html
+++ b/docs/src/view.map.html
@@ -1,15 +1,16 @@
- 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 || {};
-( function ( $ , my ) { Map view for a Dataset using Leaflet mapping library.
+( function ( $ , my ) {
+ "use strict" ; 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 in 2 ways:
-Via a single field. This field must be either a geo_point or
+ Via a single field. This field must be either a geo_point or
GeoJSON object
Via two fields with latitude and longitude coordinates.
@@ -28,6 +29,8 @@
latField: {id of field containing latitude in the dataset}
autoZoom: true,
// use cluster support
+ // cluster: true = always on
+ // cluster: false = always off
cluster: false
}
@@ -49,7 +52,6 @@
initialize : function ( options ) {
var self = this ;
- this . el = $ ( this . el );
this . visible = true ;
this . mapReady = false ; this will be the Leaflet L.Map object (setup below)
this . map = null ;
@@ -69,29 +71,29 @@
singleMarkerMode : false ,
skipDuplicateAddTesting : true ,
animateAddingMarkers : false
- }; Listen to changes in the fields
this . model . fields . bind ( 'change' , function () {
+ }; Listen to changes in the fields
this . listenTo ( this . model . fields , 'change' , function () {
self . _setupGeometryField ();
self . render ();
- }); Listen to changes in the records
this . model . records . bind ( 'add' , function ( doc ){ self . redraw ( 'add' , doc );});
- this . model . records . bind ( 'change' , function ( doc ){
+ }); Listen to changes in the records
this . listenTo ( this . model . records , 'add' , function ( doc ){ self . redraw ( 'add' , doc );});
+ this . listenTo ( this . model . records , 'change' , function ( doc ){
self . redraw ( 'remove' , doc );
self . redraw ( 'add' , doc );
});
- this . model . records . bind ( 'remove' , function ( doc ){ self . redraw ( 'remove' , doc );});
- this . model . records . bind ( 'reset' , function (){ self . redraw ( 'reset' );});
+ this . listenTo ( this . model . records , 'remove' , function ( doc ){ self . redraw ( 'remove' , doc );});
+ this . listenTo ( this . model . records , 'reset' , function (){ self . redraw ( 'reset' );});
this . menu = new my . MapMenu ({
model : this . model ,
state : this . state . toJSON ()
});
- this . menu . state . bind ( 'change' , function () {
+ this . listenTo ( this . menu . state , 'change' , function () {
self . state . set ( self . menu . state . toJSON ());
self . redraw ();
});
- this . state . bind ( 'change' , function () {
+ this . listenTo ( this . state , 'change' , function () {
self . redraw ();
});
- this . elSidebar = this . menu . el ;
+ this . elSidebar = this . menu . $el ;
}, Customization Functions
The following methods are designed for overriding in order to customize
@@ -107,7 +109,7 @@
}
infobox : function ( record ) {
var html = '' ;
- for ( key in record . attributes ){
+ for ( var key in record . attributes ){
if ( ! ( this . state . get ( 'geomField' ) && key == this . state . get ( 'geomField' ))){
html += '<div><strong>' + key + '</strong>: ' + record . attributes [ key ] + '</div>' ;
}
@@ -142,10 +144,9 @@
Also sets up the editor fields and the map if necessary.
render : function () {
var self = this ;
-
- htmls = Mustache . render ( this . template , this . model . toTemplateJSON ());
- $ ( this . el ). html ( htmls );
- this . $map = this . el . find ( '.panel.map' );
+ var htmls = Mustache . render ( this . template , this . model . toTemplateJSON ());
+ this . $el . html ( htmls );
+ this . $map = this . $el . find ( '.panel.map' );
this . redraw ();
return this ;
}, Public: Redraws the features on the map according to the action provided
@@ -180,13 +181,7 @@
this . _add ( doc );
} else if ( action == 'remove' && doc ){
this . _remove ( doc );
- } 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!
+ }
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 );
@@ -204,7 +199,7 @@
}
},
- 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' )) {
@@ -221,7 +216,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
@@ -237,10 +232,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
};
@@ -262,20 +257,41 @@
}
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 ;
if ( ! ( docs instanceof Array )) docs = [ docs ];
_ . each ( docs , function ( doc ){
- for ( key in self . features . _layers ){
+ for ( var key in self . features . _layers ){
if ( self . features . _layers [ key ]. feature . properties . cid == doc . cid ){
self . features . removeLayer ( self . features . _layers [ key ]);
}
}
});
+ }, Private: convert DMS coordinates to decimal
+
+north and east are positive, south and west are negative
_parseCoordinateString : function ( coord ){
+ if ( typeof ( coord ) != 'string' ) {
+ return ( parseFloat ( coord ));
+ }
+ var dms = coord . split ( /[^\.\d\w]+/ );
+ var deg = 0 ; var m = 0 ;
+ var toDeg = [ 1 , 60 , 3600 ]; // conversion factors for Deg, min, sec
+ var i ;
+ for ( i = 0 ; i < dms . length ; ++ i ) {
+ if ( isNaN ( parseFloat ( dms [ i ]))) {
+ continue ;
+ }
+ deg += parseFloat ( dms [ i ]) / toDeg [ m ];
+ m += 1 ;
+ }
+ if ( coord . match ( /[SW]/ )) {
+ deg = - 1 * deg ;
+ }
+ return ( deg );
}, 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' ));
@@ -283,12 +299,12 @@
value = $ . parseJSON ( value );
} catch ( e ) {}
}
-
if ( typeof ( value ) === 'string' ) {
value = value . replace ( '(' , '' ). replace ( ')' , '' );
var parts = value . split ( ',' );
- var lat = parseFloat ( parts [ 0 ]);
- var lon = parseFloat ( parts [ 1 ]);
+ var lat = this . _parseCoordinateString ( parts [ 0 ]);
+ var lon = this . _parseCoordinateString ( parts [ 1 ]);
+
if ( ! isNaN ( lon ) && ! isNaN ( parseFloat ( lat ))) {
return {
"type" : "Point" ,
@@ -308,6 +324,9 @@
} 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' ));
+ lon = this . _parseCoordinateString ( lon );
+ lat = this . _parseCoordinateString ( lat );
+
if ( ! isNaN ( parseFloat ( lon )) && ! isNaN ( parseFloat ( lat ))) {
return {
type : 'Point' ,
@@ -353,8 +372,8 @@
var self = this ;
this . map = new L . Map ( this . $map . get ( 0 ));
- var mapUrl = "http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png" ;
- var osmAttribution = 'Map data © 2011 OpenStreetMap contributors, Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">' ;
+ var mapUrl = "//otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png" ;
+ var osmAttribution = 'Map data © 2011 OpenStreetMap contributors, Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="//developer.mapquest.com/content/osm/mq_logo.png">' ;
var bg = new L . TileLayer ( mapUrl , { maxZoom : 18 , attribution : osmAttribution , subdomains : '1234' });
this . map . addLayer ( bg );
@@ -437,7 +456,6 @@
Cluster markers</label> \
</div> \
<input type="hidden" class="editor-id" value="map-1" /> \
- </div> \
</form> \
' , Define here events for UI elements
events : {
'click .editor-update-map' : 'onEditorSubmit' ,
@@ -448,38 +466,37 @@
initialize : function ( options ) {
var self = this ;
- this . el = $ ( this . el );
_ . bindAll ( this , 'render' );
- this . model . fields . bind ( 'change' , this . render );
+ this . listenTo ( this . model . fields , 'change' , this . render );
this . state = new recline . Model . ObjectState ( options . state );
- this . state . bind ( 'change' , this . render );
+ this . listenTo ( this . state , 'change' , this . render );
this . render ();
}, Public: Adds the necessary elements to the page.
Also sets up the editor fields and the map if necessary.
render : function () {
var self = this ;
- htmls = Mustache . render ( this . template , this . model . toTemplateJSON ());
- $ ( this . el ). html ( htmls );
+ var htmls = Mustache . render ( this . template , this . model . toTemplateJSON ());
+ this . $el . html ( htmls );
if ( this . _geomReady () && this . model . fields . length ){
if ( this . state . get ( 'geomField' )){
this . _selectOption ( 'editor-geom-field' , this . state . get ( 'geomField' ));
- this . el . find ( '#editor-field-type-geom' ). attr ( 'checked' , 'checked' ). change ();
+ this . $el . find ( '#editor-field-type-geom' ). attr ( 'checked' , 'checked' ). change ();
} else {
this . _selectOption ( 'editor-lon-field' , this . state . get ( 'lonField' ));
this . _selectOption ( 'editor-lat-field' , this . state . get ( 'latField' ));
- this . el . find ( '#editor-field-type-latlon' ). attr ( 'checked' , 'checked' ). change ();
+ this . $el . find ( '#editor-field-type-latlon' ). attr ( 'checked' , 'checked' ). change ();
}
}
if ( this . state . get ( 'autoZoom' )) {
- this . el . find ( '#editor-auto-zoom' ). attr ( 'checked' , 'checked' );
+ this . $el . find ( '#editor-auto-zoom' ). attr ( 'checked' , 'checked' );
} else {
- this . el . find ( '#editor-auto-zoom' ). removeAttr ( 'checked' );
+ this . $el . find ( '#editor-auto-zoom' ). removeAttr ( 'checked' );
}
if ( this . state . get ( 'cluster' )) {
- this . el . find ( '#editor-cluster' ). attr ( 'checked' , 'checked' );
+ this . $el . find ( '#editor-cluster' ). attr ( 'checked' , 'checked' );
} else {
- this . el . find ( '#editor-cluster' ). removeAttr ( 'checked' );
+ this . $el . find ( '#editor-cluster' ). removeAttr ( 'checked' );
}
return this ;
},
@@ -491,28 +508,28 @@
Right now the only configurable option is what field(s) contains the
location information.
onEditorSubmit : function ( e ){
e . preventDefault ();
- if ( this . el . find ( '#editor-field-type-geom' ). attr ( 'checked' )){
+ if ( this . $el . find ( '#editor-field-type-geom' ). attr ( 'checked' )){
this . state . set ({
- geomField : this . el . find ( '.editor-geom-field > select > option:selected' ). val (),
+ geomField : this . $el . find ( '.editor-geom-field > select > option:selected' ). val (),
lonField : null ,
latField : null
});
} else {
this . state . set ({
geomField : null ,
- lonField : this . el . find ( '.editor-lon-field > select > option:selected' ). val (),
- latField : this . el . find ( '.editor-lat-field > select > option:selected' ). val ()
+ lonField : this . $el . find ( '.editor-lon-field > select > option:selected' ). val (),
+ latField : this . $el . find ( '.editor-lat-field > select > option:selected' ). val ()
});
}
return false ;
}, 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 ();
- this . el . find ( '.editor-field-type-latlon' ). hide ();
+ this . $el . find ( '.editor-field-type-geom' ). show ();
+ this . $el . find ( '.editor-field-type-latlon' ). hide ();
} else {
- this . el . find ( '.editor-field-type-geom' ). hide ();
- this . el . find ( '.editor-field-type-latlon' ). show ();
+ this . $el . find ( '.editor-field-type-geom' ). hide ();
+ this . $el . find ( '.editor-field-type-latlon' ). show ();
}
},
@@ -523,7 +540,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 ){
- var options = this . el . find ( '.' + id + ' > select > option' );
+ var options = this . $el . find ( '.' + id + ' > select > option' );
if ( options ){
options . each ( function ( opt ){
if ( this . value == value ) {
diff --git a/docs/src/view.multiview.html b/docs/src/view.multiview.html
index 0bba6c7f3..a49cd9ab1 100644
--- a/docs/src/view.multiview.html
+++ b/docs/src/view.multiview.html
@@ -1,7 +1,8 @@
- 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
+( function ( $ , my ) {
+ "use strict" ; MultiView
Manage multiple views together along with query editor etc. Usage:
@@ -124,7 +125,6 @@ Parameters
initialize : function ( options ) {
var self = this ;
- this . el = $ ( this . el );
this . _setupState ( options . state ); Hash of 'page' views (i.e. those for whole page) keyed by page name
if ( options . views ) {
this . pageViews = options . views ;
} else {
@@ -185,40 +185,36 @@ Parameters
}
this . _showHideSidebar ();
- this . model . bind ( 'query:start' , function () {
- self . notify ({ loader : true , persist : true });
- });
- this . model . bind ( 'query:done' , function () {
- self . clearNotifications ();
- self . el . find ( '.doc-count' ). text ( self . model . recordCount || 'Unknown' );
- });
- this . model . bind ( 'query:fail' , function ( error ) {
- self . clearNotifications ();
- var msg = '' ;
- if ( typeof ( error ) == 'string' ) {
- msg = error ;
- } else if ( typeof ( error ) == 'object' ) {
- if ( error . title ) {
- msg = error . title + ': ' ;
- }
- if ( error . message ) {
- msg += error . message ;
- }
- } else {
- msg = 'There was an error querying the backend' ;
+ this . listenTo ( this . model , 'query:start' , function () {
+ self . notify ({ loader : true , persist : true });
+ });
+ this . listenTo ( this . model , 'query:done' , function () {
+ self . clearNotifications ();
+ self . $el . find ( '.doc-count' ). text ( self . model . recordCount || 'Unknown' );
+ });
+ this . listenTo ( this . model , 'query:fail' , function ( error ) {
+ self . clearNotifications ();
+ var msg = '' ;
+ if ( typeof ( error ) == 'string' ) {
+ msg = error ;
+ } else if ( typeof ( error ) == 'object' ) {
+ if ( error . title ) {
+ msg = error . title + ': ' ;
}
- self . notify ({ message : msg , category : 'error' , persist : true });
- }); retrieve basic data like fields etc
+ if ( error . message ) {
+ msg += error . message ;
+ }
+ } else {
+ msg = 'There was an error querying the backend' ;
+ }
+ self . notify ({ message : msg , category : 'error' , persist : true });
+ });
retrieve basic data like fields etc
note this.model and dataset returned are the same
TODO: set query state ...?
this . model . queryState . set ( self . state . get ( 'query' ), { silent : true });
- this . model . fetch ()
- . fail ( function ( error ) {
- self . notify ({ message : error . message , category : 'error' , persist : true });
- });
},
setReadOnly : function () {
- this . el . addClass ( 'recline-read-only' );
+ this . $el . addClass ( 'recline-read-only' );
},
render : function () {
@@ -226,8 +222,8 @@ Parameters
tmplData . views = this . pageViews ;
tmplData . sidebarViews = this . sidebarViews ;
var template = Mustache . render ( this . template , tmplData );
- $ ( this . el ). html ( template ); now create and append other views
var $dataViewContainer = this . el . find ( '.data-view-container' );
- var $dataSidebar = this . el . find ( '.data-view-sidebar' ); the main views
_ . each ( this . pageViews , function ( view , pageName ) {
+ this . $el . html ( template ); now create and append other views
var $dataViewContainer = this . $el . find ( '.data-view-container' );
+ var $dataSidebar = this . $el . find ( '.data-view-sidebar' ); the main views
_ . each ( this . pageViews , function ( view , pageName ) {
view . view . render ();
$dataViewContainer . append ( view . view . el );
if ( view . view . elSidebar ) {
@@ -236,22 +232,34 @@ Parameters
});
_ . each ( this . sidebarViews , function ( view ) {
- this [ '$' + view . id ] = view . view . el ;
+ this [ '$' + view . id ] = view . view . $el ;
$dataSidebar . append ( view . view . el );
}, this );
- var pager = new recline . View . Pager ({
- model : this . model . queryState
+ this . pager = new recline . View . Pager ({
+ model : this . model
});
- this . el . find ( '.recline-results-info' ). after ( pager . el );
+ this . $el . find ( '.recline-results-info' ). after ( this . pager . el );
- var queryEditor = new recline . View . QueryEditor ({
+ this . queryEditor = new recline . View . QueryEditor ({
model : this . model . queryState
});
- this . el . find ( '.query-editor-here' ). append ( queryEditor . el );
+ this . $el . find ( '.query-editor-here' ). append ( this . queryEditor . el );
+
+ },
+ remove : function () {
+ _ . each ( this . pageViews , function ( view ) {
+ view . view . remove ();
+ });
+ _ . each ( this . sidebarViews , function ( view ) {
+ view . view . remove ();
+ });
+ this . pager . remove ();
+ this . queryEditor . remove ();
+ Backbone . View . prototype . remove . apply ( this , arguments );
}, hide the sidebar if empty
_showHideSidebar : function () {
- var $dataSidebar = this . el . find ( '.data-view-sidebar' );
+ var $dataSidebar = this . $el . find ( '.data-view-sidebar' );
var visibleChildren = $dataSidebar . children (). filter ( function () {
return $ ( this ). css ( "display" ) != "none" ;
}). length ;
@@ -264,16 +272,16 @@ Parameters
},
updateNav : function ( pageName ) {
- this . el . find ( '.navigation a' ). removeClass ( 'active' );
- var $el = this . el . find ( '.navigation a[data-view="' + pageName + '"]' );
+ this . $el . find ( '.navigation a' ). removeClass ( 'active' );
+ var $el = this . $el . find ( '.navigation a[data-view="' + pageName + '"]' );
$el . addClass ( 'active' ); add/remove sidebars and hide inactive views
_ . each ( this . pageViews , function ( view , idx ) {
if ( view . id === pageName ) {
- view . view . el . show ();
+ view . view . $el . show ();
if ( view . view . elSidebar ) {
view . view . elSidebar . show ();
}
} else {
- view . view . el . hide ();
+ view . view . $el . hide ();
if ( view . view . elSidebar ) {
view . view . elSidebar . hide ();
}
@@ -327,7 +335,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 . listenTo ( this . model . queryState , 'change' , function () {
self . state . set ({ query : self . model . queryState . toJSON ()});
});
_ . each ( this . pageViews , function ( pageView ) {
@@ -335,7 +343,7 @@ Parameters
var update = {};
update [ 'view-' + pageView . id ] = pageView . view . state . toJSON ();
self . state . set ( update );
- pageView . view . state . bind ( 'change' , function () {
+ self . listenTo ( pageView . view . state , '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 });
self . state . trigger ( 'change' );
@@ -347,7 +355,7 @@ Parameters
_bindFlashNotifications : function () {
var self = this ;
_ . each ( this . pageViews , function ( pageView ) {
- pageView . view . bind ( 'recline:flash' , function ( flash ) {
+ self . listenTo ( pageView . view , 'recline:flash' , function ( flash ) {
self . notify ( flash );
});
});
@@ -450,7 +458,7 @@ Parameters
}
return urlParams ;
}; Parse the query string out of the URL hash
my . parseHashQueryString = function () {
- q = my . parseHashUrl ( window . location . hash ). query ;
+ var q = my . parseHashUrl ( window . location . hash ). query ;
return my . parseQueryString ( q );
}; Compse a Query String
my . composeQueryString = function ( queryParams ) {
var queryString = '?' ;
diff --git a/docs/src/view.slickgrid.html b/docs/src/view.slickgrid.html
index 08cab3a1d..110e8ea5d 100644
--- a/docs/src/view.slickgrid.html
+++ b/docs/src/view.slickgrid.html
@@ -1,9 +1,36 @@
- 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 || {};
-( function ( $ , my ) { SlickGrid Dataset View
+( function ( $ , my ) {
+ "use strict" ; Add new grid Control to display a new row add menu bouton
+It display a simple side-bar menu ,for user to add new
+row to grid
my . GridControl = Backbone . View . extend ({
+ className : "recline-row-add" , Template for row edit menu , change it if you don't love
template : '<h1><a href="#" class="recline-row-add btn">Add row</a></h1>' ,
+
+ initialize : function ( options ){
+ var self = this ;
+ _ . bindAll ( this , 'render' );
+ this . state = new recline . Model . ObjectState ();
+ this . render ();
+ },
+
+ render : function () {
+ var self = this ;
+ this . $el . html ( this . template )
+ },
+
+ events : {
+ "click .recline-row-add" : "addNewRow"
+ },
+
+ addNewRow : function ( e ){
+ e . preventDefault ()
+ this . state . trigger ( "change" )
+ }
+ }
+ ); SlickGrid Dataset View
Provides a tabular view on a Dataset, based on SlickGrid.
@@ -21,7 +48,11 @@
model: dataset,
el: $el,
state: {
- gridOptions: {editable: true},
+ gridOptions: {
+ editable: true,
+ enableAddRows: true
+ ...
+ },
columnsEditor: [
{column: 'date', editor: Slick.Editors.Date },
{column: 'title', editor: Slick.Editors.Text}
@@ -31,14 +62,13 @@
// 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 );
- this . el . addClass ( 'recline-slickgrid' );
- _ . bindAll ( this , 'render' );
- 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 );
-
+ this . $el . addClass ( 'recline-slickgrid' );
+ Template for row delete menu , change it if you don't love
this . templates = {
+ "deleterow" : '<a href="#" class="recline-row-delete btn">X</a>'
+ }
+ _ . bindAll ( this , 'render' , 'onRecordChanged' );
+ this . listenTo ( this . model . records , 'add remove reset' , this . render );
+ this . listenTo ( this . model . records , 'change' , this . onRecordChanged );
var state = _ . extend ({
hiddenColumns : [],
columnsOrder : [],
@@ -51,38 +81,67 @@
);
this . state = new recline . Model . ObjectState ( state );
+ this . _slickHandler = new Slick . EventHandler (); add menu for new row , check if enableAddRow is set to true or not set
if ( this . state . get ( "gridOptions" )
+ && this . state . get ( "gridOptions" ). enabledAddRow != undefined
+ && this . state . get ( "gridOptions" ). enabledAddRow == true ){
+ this . editor = new my . GridControl ()
+ this . elSidebar = this . editor . $el
+ this . listenTo ( this . editor . state , 'change' , function (){
+ this . model . records . add ( new recline . Model . Record ())
+ });
+ }
},
-
- events : {
- },
-
- onRecordChanged : function ( record ) { Ignore if the grid is not yet drawn
if ( ! this . grid ) {
+ 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 );
+ } 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 () {
+ render : function () {
var self = this ;
-
var options = _ . extend ({
enableCellNavigation : true ,
enableColumnReorder : true ,
explicitInitialization : true ,
syncColumnCellResize : true ,
forceFitColumns : this . state . get ( 'fitColumns' )
- }, 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
+ }, 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 );
+ if ( columnDef . id == "del" ){
+ return self . templates . deleterow
+ }
+ var field = self . model . fields . get ( columnDef . id );
if ( field . renderer ) {
- return field . renderer ( value , field , dataContext );
- } else {
- return value ;
+ return field . renderer ( value , field , dataContext );
+ } else {
+ return value
}
- };
+ }; we need to be sure that user is entering a valid input , for exemple if
+field is date type and field.format ='YY-MM-DD', we should be sure that
+user enter a correct value
var validator = function ( field ){
+ return function ( value ){
+ if ( field . type == "date" && isNaN ( Date . parse ( value ))){
+ return {
+ valid : false ,
+ msg : "A date is required, check field field-date-format" };
+ } else {
+ return { valid : true , msg : null }
+ }
+ }
+ }; Add row delete support , check if enableDelRow is set to true or not set
if ( this . state . get ( "gridOptions" )
+ && this . state . get ( "gridOptions" ). enabledDelRow != undefined
+ && this . state . get ( "gridOptions" ). enabledDelRow == true ){
+ columns . push ({
+ id : 'del' ,
+ name : 'del' ,
+ field : 'del' ,
+ sortable : true ,
+ width : 80 ,
+ formatter : formatter ,
+ validator : validator
+ })}
_ . each ( this . model . fields . toJSON (), function ( field ){
var column = {
id : field . id ,
@@ -90,39 +149,53 @@
field : field . id ,
sortable : true ,
minWidth : 80 ,
- formatter : formatter
+ formatter : formatter ,
+ validator : validator ( field )
};
-
var widthInfo = _ . find ( self . state . get ( 'columnsWidth' ), function ( c ){ return c . column === field . id ;});
if ( widthInfo ){
column . width = widthInfo . width ;
}
-
var editInfo = _ . find ( self . state . get ( 'columnsEditor' ), function ( c ){ return c . column === field . id ;});
if ( editInfo ){
column . editor = editInfo . editor ;
+ } else { guess editor type
var typeToEditorMap = {
+ 'string' : Slick . Editors . LongText ,
+ 'integer' : Slick . Editors . IntegerEditor ,
+ 'number' : Slick . Editors . Text , TODO: need a way to ensure we format date in the right way
+Plus what if dates are in distant past or future ... (?)
+'date': Slick.Editors.DateEditor,
'date' : Slick . Editors . Text ,
+ 'boolean' : Slick . Editors . YesNoSelectEditor TODO: (?) percent ...
};
+ if ( field . type in typeToEditorMap ) {
+ column . editor = typeToEditorMap [ field . type ]
+ } else {
+ column . editor = Slick . Editors . LongText ;
+ }
}
columns . push ( column );
- }); Restrict the visible columns
var visibleColumns = columns . filter ( function ( column ) {
+ }); Restrict the visible columns
var visibleColumns = _ . filter ( columns , 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 ) {
+ }); 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 ){
tempHiddenColumns . push ( columns . splice ( i , 1 )[ 0 ]);
}
}
- columns = columns . concat ( tempHiddenColumns ); Transform a model object into a row
function toRow ( m ) {
+ 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 ] = m . getFieldValueUnrendered ( field );
+ var render = "" ; when adding row from slickgrid the field value is undefined
if ( ! _ . isUndefined ( m . getFieldValueUnrendered ( field ))){
+ render = m . getFieldValueUnrendered ( field )
+ }
+ row [ field . id ] = render
});
return row ;
}
@@ -140,11 +213,12 @@
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 . getModelRow = function ( m ) { return _ . indexOf ( models , m );};
this . updateItem = function ( m , i ) {
rows [ i ] = toRow ( m );
models [ i ] = m ;
};
+
}
var data = new RowSet ();
@@ -153,14 +227,14 @@
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' ;
this . grid . setSortColumn ( column , sortAsc );
}
- this . grid . onSort . subscribe ( function ( e , args ){
+ this . _slickHandler . subscribe ( this . grid . onSort , function ( e , args ){
var order = ( args . sortAsc ) ? 'asc' : 'desc' ;
var sort = [{
field : args . sortCol . field ,
@@ -169,7 +243,7 @@
self . model . query ({ sort : sort });
});
- this . grid . onColumnsReordered . subscribe ( function ( e , args ){
+ this . _slickHandler . subscribe ( this . grid . onColumnsReordered , function ( e , args ){
self . state . set ({ columnsOrder : _ . pluck ( self . grid . getColumns (), 'id' )});
});
@@ -184,28 +258,38 @@
});
self . state . set ({ columnsWidth : columnsWidth });
});
-
- this . grid . onCellChange . subscribe ( function ( e , args ) { We need to change the model associated value
var grid = args . grid ;
+
+ this . _slickHandler . subscribe ( this . grid . onCellChange , 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 ,
+ });
+ this . _slickHandler . subscribe ( this . grid . onClick , function ( e , args ){
+ if ( args . cell == 0 && self . state . get ( "gridOptions" ). enabledDelRow == true ){ We need to delete the associated model
var model = data . getModel ( args . row );
+ model . destroy ()
+ }
+ }) ;
+
+ 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 ;
- },
+ },
+
+ remove : function () {
+ this . _slickHandler . unsubscribeAll ();
+ Backbone . View . prototype . remove . apply ( this , arguments );
+ },
- 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 ();
@@ -336,7 +420,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 6f903f8bf..f86f6c452 100644
--- a/docs/src/view.timeline.html
+++ b/docs/src/view.timeline.html
@@ -1,9 +1,10 @@
- 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 || {};
-( function ( $ , my ) { turn off unnecessary logging from VMM Timeline
if ( typeof VMM !== 'undefined' ) {
+( function ( $ , my ) {
+ "use strict" ; turn off unnecessary logging from VMM Timeline
if ( typeof VMM !== 'undefined' ) {
VMM . debug = false ;
} Timeline
@@ -19,18 +20,18 @@
initialize : function ( options ) {
var self = this ;
- this . el = $ ( this . el );
- this . timeline = new VMM . Timeline ();
+ this . timeline = new VMM . Timeline ( this . elementId );
this . _timelineIsInitialized = false ;
- this . model . fields . bind ( 'reset' , function () {
+ this . listenTo ( this . model . fields , 'reset' , function () {
self . _setupTemporalField ();
});
- this . model . records . bind ( 'all' , function () {
+ this . listenTo ( this . model . records , 'all' , function () {
self . reloadData ();
});
var stateData = _ . extend ({
startField : null ,
- endField : null ,
+ endField : null , by default timelinejs (and browsers) will parse ambiguous dates in US format (mm/dd/yyyy)
+set to true to interpret dd/dd/dddd as dd/mm/yyyy
nonUSDates : false ,
timelineJSOptions : {}
},
options . state
@@ -42,21 +43,22 @@
render : function () {
var tmplData = {};
var htmls = Mustache . render ( this . template , tmplData );
- this . el . html ( htmls ); can only call _initTimeline once view in DOM as Timeline uses $
+ this . $el . html ( htmls );
can only call _initTimeline once view in DOM as Timeline uses $
internally to look up element
if ( $ ( this . elementId ). length > 0 ) {
this . _initTimeline ();
}
},
- show : function () { only call _initTimeline once view in DOM as Timeline uses $ internally to look up element
if ( this . _timelineIsInitialized === false ) {
+ show : function () { only call _initTimeline once view in DOM as Timeline uses $ internally to look up element
if ( this . _timelineIsInitialized === false ) {
this . _initTimeline ();
}
},
_initTimeline : function () {
- var $timeline = this . el . find ( this . elementId );
var data = this . _timelineJSON ();
- this . timeline . init ( data , this . elementId , this . state . get ( "timelineJSOptions" ));
+ var config = this . state . get ( "timelineJSOptions" );
+ config . id = this . elementId ;
+ this . timeline . init ( config , data );
this . _timelineIsInitialized = true
},
@@ -65,11 +67,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 ) {
@@ -100,7 +102,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!'
@@ -108,21 +110,32 @@
out . timeline . date . push ( tlEntry );
}
return out ;
- },
-
- _parseDate : function ( date ) {
+ }, convert dates into a format TimelineJS will handle
+TimelineJS does not document this at all so combo of read the code +
+trial and error
+Summary (AFAICt):
+Preferred: [-]yyyy[,mm,dd,hh,mm,ss]
+Supported: mm/dd/yyyy
_parseDate : function ( date ) {
if ( ! date ) {
return null ;
}
- var out = date . trim ();
+ var out = $ . trim ( date );
out = out . replace ( /(\d)th/g , '$1' );
out = out . replace ( /(\d)st/g , '$1' );
- out = out . trim () ? moment ( out ) : null ;
- if ( out . toDate () == 'Invalid Date' ) {
- return null ;
- } else {
- return out . toDate ();
+ out = $ . trim ( out );
+ if ( out . match ( /\d\d\d\d-\d\d-\d\d(T.*)?/ )) {
+ out = out . replace ( /-/g , ',' ). replace ( 'T' , ',' ). replace ( ':' , ',' );
}
+ if ( out . match ( /\d\d-\d\d-\d\d.*/ )) {
+ out = out . replace ( /-/g , '/' );
+ }
+ if ( this . state . get ( 'nonUSDates' )) {
+ var parts = out . match ( /(\d\d)\/(\d\d)\/(\d\d.*)/ );
+ if ( parts ) {
+ out = [ parts [ 2 ], parts [ 1 ], parts [ 3 ]]. join ( '/' );
+ }
+ }
+ return out ;
},
_setupTemporalField : function () {
diff --git a/docs/src/widget.facetviewer.html b/docs/src/widget.facetviewer.html
index 57ef47afc..ddc3ab72f 100644
--- a/docs/src/widget.facetviewer.html
+++ b/docs/src/widget.facetviewer.html
@@ -1,9 +1,10 @@
- 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 || {};
-( function ( $ , my ) { FacetViewer
+( function ( $ , my ) {
+ "use strict" ; FacetViewer
Widget for displaying facets
@@ -39,9 +40,8 @@
},
initialize : function ( model ) {
_ . bindAll ( this , 'render' );
- this . el = $ ( this . el );
- this . model . facets . bind ( 'all' , this . render );
- this . model . fields . bind ( 'all' , this . render );
+ this . listenTo ( this . model . facets , 'all' , this . render );
+ this . listenTo ( this . model . fields , 'all' , this . render );
this . render ();
},
render : function () {
@@ -58,15 +58,15 @@
return facet ;
});
var templated = Mustache . render ( this . template , tmplData );
- this . el . html ( templated ); are there actually any facets to show?
if ( this . model . facets . length > 0 ) {
- this . el . show ();
+ this . $el . html ( templated ); are there actually any facets to show?
if ( this . model . facets . length > 0 ) {
+ this . $el . show ();
} else {
- this . el . hide ();
+ this . $el . hide ();
}
},
onHide : function ( e ) {
e . preventDefault ();
- this . el . hide ();
+ this . $el . hide ();
},
onFacetFilter : function ( e ) {
e . preventDefault ();
diff --git a/docs/src/widget.fields.html b/docs/src/widget.fields.html
index c98b3c4ba..d4fe22822 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
@@ -10,7 +10,8 @@
this . recline . View = this . recline . View || {};
( function ( $ , my ) {
-
+ "use strict" ;
+
my . Fields = Backbone . View . extend ({
className : 'recline-fields-view' ,
template : ' \
@@ -49,17 +50,16 @@
initialize : function ( model ) {
var self = this ;
- this . el = $ ( this . el );
_ . bindAll ( this , 'render' ); TODO: this is quite restrictive in terms of when it is re-run
e.g. a change in type will not trigger a re-run atm.
-being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width)
this . model . fields . bind ( 'reset' , function ( action ) {
+being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width) this . listenTo ( this . model . fields , 'reset' , function ( action ) {
self . model . fields . each ( function ( field ) {
field . facets . unbind ( 'all' , self . render );
field . facets . bind ( 'all' , self . render );
}); fields can get reset or changed in which case we need to recalculate
self . model . getFieldsSummary ();
self . render ();
});
- this . el . find ( '.collapse' ). collapse ();
+ this . $el . find ( '.collapse' ). collapse ();
this . render ();
},
render : function () {
@@ -73,7 +73,7 @@
tmplData . fields . push ( out );
});
var templated = Mustache . render ( this . template , tmplData );
- this . el . html ( templated );
+ this . $el . html ( templated );
}
});
diff --git a/docs/src/widget.filtereditor.html b/docs/src/widget.filtereditor.html
index 4d0079b34..d4bf97bb9 100644
--- a/docs/src/widget.filtereditor.html
+++ b/docs/src/widget.filtereditor.html
@@ -1,9 +1,10 @@
- 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 || {};
( function ( $ , my ) {
+ "use strict" ;
my . FilterEditor = Backbone . View . extend ({
className : 'recline-filter-editor well' ,
@@ -58,9 +59,9 @@
<a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">×</a> \
</legend> \
<label class="control-label" for="">From</label> \
- <input type="text" value="{{start}}" name="start" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
+ <input type="text" value="{{from}}" name="from" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
<label class="control-label" for="">To</label> \
- <input type="text" value="{{stop}}" name="stop" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
+ <input type="text" value="{{to}}" name="to" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
</fieldset> \
</div> \
' ,
@@ -88,11 +89,9 @@
'submit form.js-add' : 'onAddFilter'
},
initialize : function () {
- this . el = $ ( this . el );
_ . bindAll ( this , 'render' );
- this . model . fields . bind ( 'all' , this . render );
- this . model . queryState . bind ( 'change' , this . render );
- this . model . queryState . bind ( 'change:filters:new-blank' , this . render );
+ this . listenTo ( this . model . fields , 'all' , this . render );
+ this . listenTo ( this . model . queryState , 'change change:filters:new-blank' , this . render );
this . render ();
},
render : function () {
@@ -106,13 +105,13 @@
return Mustache . render ( self . filterTemplates [ this . type ], this );
};
var out = Mustache . render ( this . template , tmplData );
- this . el . html ( out );
+ this . $el . html ( out );
},
onAddFilterShow : function ( e ) {
e . preventDefault ();
var $target = $ ( e . target );
$target . hide ();
- this . el . find ( 'form.js-add' ). show ();
+ this . $el . find ( 'form.js-add' ). show ();
},
onAddFilter : function ( e ) {
e . preventDefault ();
diff --git a/docs/src/widget.pager.html b/docs/src/widget.pager.html
index 5956b87e4..c03c3d98d 100644
--- a/docs/src/widget.pager.html
+++ b/docs/src/widget.pager.html
@@ -1,9 +1,10 @@
- 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 || {};
( function ( $ , my ) {
+ "use strict" ;
my . Pager = Backbone . View . extend ({
className : 'recline-pager' ,
@@ -24,35 +25,43 @@
initialize : function () {
_ . bindAll ( this , 'render' );
- this . el = $ ( this . el );
- this . model . bind ( 'change' , this . render );
+ this . listenTo ( this . model . queryState , 'change' , this . render );
this . render ();
},
onFormSubmit : function ( e ) {
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 });
+ var newFrom = parseInt ( this . $el . find ( 'input[name="from"]' ). val ());
+ newFrom = Math . min ( this . model . recordCount , Math . max ( newFrom , 1 )) - 1 ;
+ var newSize = parseInt ( this . $el . find ( 'input[name="to"]' ). val ()) - newFrom ;
+ newSize = Math . min ( Math . max ( newSize , 1 ), this . model . recordCount );
+ this . model . queryState . set ({ size : newSize , from : newFrom });
},
onPaginationUpdate : function ( e ) {
e . preventDefault ();
var $el = $ ( e . target );
var newFrom = 0 ;
+ var currFrom = this . model . queryState . get ( 'from' );
+ var size = this . model . queryState . get ( 'size' );
+ var updateQuery = false ;
if ( $el . parent (). hasClass ( 'prev' )) {
- newFrom = this . model . get ( 'from' ) - Math . max ( 0 , this . model . get ( 'size' ));
+ newFrom = Math . max ( currFrom - Math . max ( 0 , size ), 1 ) - 1 ;
+ updateQuery = newFrom != currFrom ;
} else {
- newFrom = this . model . get ( 'from' ) + this . model . get ( 'size' );
+ newFrom = Math . max ( currFrom + size , 1 );
+ updateQuery = ( newFrom < this . model . recordCount );
+ }
+ if ( updateQuery ) {
+ this . model . queryState . set ({ from : newFrom });
}
- newFrom = Math . max ( newFrom , 0 );
- this . model . set ({ from : newFrom });
},
render : function () {
var tmplData = this . model . toJSON ();
- tmplData . to = this . model . get ( 'from' ) + this . model . get ( 'size' );
+ var from = parseInt ( this . model . queryState . get ( 'from' ));
+ tmplData . from = from + 1 ;
+ tmplData . to = Math . min ( from + this . model . queryState . get ( 'size' ), this . model . recordCount );
var templated = Mustache . render ( this . template , tmplData );
- this . el . html ( templated );
+ this . $el . html ( templated );
+ return this ;
}
});
diff --git a/docs/src/widget.queryeditor.html b/docs/src/widget.queryeditor.html
index 0684637d7..79262cf0d 100644
--- a/docs/src/widget.queryeditor.html
+++ b/docs/src/widget.queryeditor.html
@@ -1,9 +1,10 @@
- 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 || {};
( function ( $ , my ) {
+ "use strict" ;
my . QueryEditor = Backbone . View . extend ({
className : 'recline-query-editor' ,
@@ -23,19 +24,18 @@
initialize : function () {
_ . bindAll ( this , 'render' );
- this . el = $ ( this . el );
- this . model . bind ( 'change' , this . render );
+ this . listenTo ( this . model , 'change' , this . render );
this . render ();
},
onFormSubmit : function ( e ) {
e . preventDefault ();
- var query = this . el . find ( '.text-query input' ). val ();
+ var query = this . $el . find ( '.text-query input' ). val ();
this . model . set ({ q : query });
},
render : function () {
var tmplData = this . model . toJSON ();
var templated = Mustache . render ( this . template , tmplData );
- this . el . html ( templated );
+ this . $el . html ( templated );
}
});