From 8f8628251743e56a13128f14d2949dc8415df176 Mon Sep 17 00:00:00 2001 From: Sol Villar Date: Fri, 6 Mar 2015 15:32:03 -0300 Subject: [PATCH] [#469] Update of dist and doc files. --- dist/recline.css | 179 ++- dist/recline.dataset.min.js | 1 + dist/recline.js | 192 ++- dist/recline.min.js | 3 + docs/src/backend.dataproxy.html | 317 +++- docs/src/backend.memory.html | 785 +++++++-- docs/src/docco.css | 640 ++++++-- docs/src/ecma-fixes.html | 279 +++- docs/src/model.html | 1772 +++++++++++++++------ docs/src/public/fonts/aller-bold.eot | Bin 0 -> 29804 bytes docs/src/public/fonts/aller-bold.ttf | Bin 0 -> 66836 bytes docs/src/public/fonts/aller-bold.woff | Bin 0 -> 33244 bytes docs/src/public/fonts/aller-light.eot | Bin 0 -> 29509 bytes docs/src/public/fonts/aller-light.ttf | Bin 0 -> 68620 bytes docs/src/public/fonts/aller-light.woff | Bin 0 -> 33124 bytes docs/src/public/fonts/roboto-black.eot | Bin 0 -> 20702 bytes docs/src/public/fonts/roboto-black.ttf | Bin 0 -> 44828 bytes docs/src/public/fonts/roboto-black.woff | Bin 0 -> 24536 bytes docs/src/public/stylesheets/normalize.css | 375 +++++ docs/src/view.flot.html | 1229 +++++++++----- docs/src/view.graph.html | 145 +- docs/src/view.grid.html | 772 ++++++--- docs/src/view.map.html | 1688 ++++++++++++++------ docs/src/view.multiview.html | 1318 ++++++++++----- docs/src/view.slickgrid.html | 1432 ++++++++++++----- docs/src/view.timeline.html | 572 +++++-- docs/src/widget.facetviewer.html | 322 +++- docs/src/widget.fields.html | 359 ++++- docs/src/widget.filtereditor.html | 486 ++++-- docs/src/widget.pager.html | 279 +++- docs/src/widget.queryeditor.html | 214 ++- docs/src/widget.valuefilter.html | 264 +++ 32 files changed, 10218 insertions(+), 3405 deletions(-) create mode 100644 dist/recline.dataset.min.js create mode 100644 dist/recline.min.js create mode 100644 docs/src/public/fonts/aller-bold.eot create mode 100644 docs/src/public/fonts/aller-bold.ttf create mode 100644 docs/src/public/fonts/aller-bold.woff create mode 100644 docs/src/public/fonts/aller-light.eot create mode 100644 docs/src/public/fonts/aller-light.ttf create mode 100644 docs/src/public/fonts/aller-light.woff create mode 100755 docs/src/public/fonts/roboto-black.eot create mode 100755 docs/src/public/fonts/roboto-black.ttf create mode 100755 docs/src/public/fonts/roboto-black.woff create mode 100644 docs/src/public/stylesheets/normalize.css create mode 100644 docs/src/widget.valuefilter.html diff --git a/dist/recline.css b/dist/recline.css index ed38db58..c716b38c 100644 --- a/dist/recline.css +++ b/dist/recline.css @@ -276,22 +276,42 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { float: right; margin-left: 5px; padding-left: 5px; - border-left: solid 2px #ddd; } -.header .recline-results-info { - line-height: 28px; +.recline-results-info { + line-height: 35px; margin-left: 20px; float: left; } +.recline-data-explorer .data-view-sidebar > div { + margin-top: 5px; + margin-bottom: 10px; +} + +.recline-data-explorer .radio, +.recline-data-explorer .checkbox { + padding-left: 20px; +} + +.recline-data-explorer .editor-update-map { + margin: 30px 0px 20px 0px; +} + +.recline-data-explorer label { + font-weight: normal; +} + /********************************************************** * Query Editor *********************************************************/ -.header .recline-query-editor { +.recline-query-editor { float: right; - height: 30px; + height: 35px; + padding-right: 5px; + margin-right: 5px; + border-right: solid 2px #ddd; } .header .input-prepend { @@ -312,11 +332,11 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { vertical-align: top; } -.header .recline-query-editor form button { +.recline-query-editor form button { vertical-align: top; } -.header .recline-query-editor label { +.recline-query-editor label { display:none; } @@ -324,28 +344,78 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { * Pager *********************************************************/ -.header .recline-pager { +.recline-pager { float: left; margin: auto; display: block; margin-left: 20px; } -.header .recline-pager .pagination label { +.recline-pager .pagination li { + display: inline-block; +} + +.recline-pager .pagination label { display:none; } -.header .recline-pager .pagination input { - width: 30px; - height: 18px; +.recline-pager .pagination input { + width: 40px; + height: 25px; padding: 2px 4px; margin: 0; - margin-top: -4px; + margin-top: -2px; + + border: 1px solid #cccccc; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + transition: border linear 0.2s, box-shadow linear 0.2s; + border-radius: 4px; + + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + -webkit-border-radius: 4px; + + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-transition: border linear 0.2s, box-shadow linear 0.2s; + -moz-border-radius: 4px; + + -o-transition: border linear 0.2s, box-shadow linear 0.2s; } -.header .recline-pager .pagination a { - line-height: 26px; - padding: 0 6px; +.recline-pager .pagination a { + float: none; + margin-left: -5px; + color: #555; +} + +.recline-pager .pagination .page-range { + height: 34px; + padding: 5px 8px; + margin-left: -5px; + border: 1px solid #ddd; +} + +.recline-pager .pagination .page-range a { + padding: 0px 12px; + border: none; +} + +.recline-pager .pagination .page-range a:hover { + background-color: #ffffff; +} + +.recline-pager .pagination > li:first-child > a { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0px; + border-top-right-radius: 0px; +} + +.recline-pager .pagination > li:last-child > a { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 0px; + border-top-left-radius: 0px; } /********************************************************** @@ -357,6 +427,31 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { display: none; } +.recline-filter-editor .filters { + margin: 20px 0px; +} + +.recline-filter-editor h3 { + margin-top: 4px; +} + +.recline-filter-editor .filter { + margin-top: 20px; +} + +.recline-filter-editor .filter .form-group { + margin-bottom: 0px; +} + +.recline-filter-editor .filter input, +.recline-filter-editor .filter label { + margin: 0px; +} + +.recline-filter-editor .js-edit button { + margin: 25px 0px 0px 0px; +} + .recline-filter-editor .filter-term a { font-size: 18px; } @@ -369,6 +464,20 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { .recline-filter-editor input { margin-top: 0.5em; + margin-bottom: 10px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: 1px solid #cccccc; +} + +.recline-filter-editor label { + font-weight: normal; + display: block; +} + +.recline-filter-editor legend { + margin-bottom: 5px; } .recline-filter-editor .add-filter { @@ -392,22 +501,30 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { padding: 0; } -.recline-fields-view .fields-list .accordion-heading, -.recline-fields-view .fields-list h3 -{ - margin: 3px 0 3px 5px; +.recline-fields-view .panel { + background-color: #f5f5f5; + border: 1px solid #e5e5e5; } -.recline-fields-view .fields-list .accordion-heading a, -.recline-fields-view .fields-list .accordion-heading h4 { +.recline-fields-view .panel-group h3 { + padding-left: 10px; +} + +.recline-fields-view .fields-list .panel-heading { + padding: 2px 5px; + margin: 1px 0px 1px 5px; +} + +.recline-fields-view .panel a, +.recline-fields-view .panel h4 { display: inline; } -.recline-fields-view .fields-list .accordion-heading a { +.recline-fields-view .panel a { padding: 0; } -.recline-fields-view .fields-list .accordion-heading h4 { +.recline-fields-view .panel h4 { word-wrap: break-word } @@ -486,6 +603,10 @@ classes should alter those! .recline-slickgrid .slick-header-column:hover, .slick-header-column-active { } +.recline-slickgrid .slick-header-column.ui-state-default { + height: 26px; +} + .recline-slickgrid .slick-headerrow { background: #fafafa; } @@ -624,16 +745,16 @@ classes should alter those! .recline-slickgrid .recline-row-delete { font-size: 12px; - padding: 2px; - width: 22px; - height: 14px; + padding: 3px; + width: 29px; + height: 18px; line-height: 13px; } .recline-cell-reorder { font-size: 12px; - padding: 2px; - width: 22px; + padding: 1px; + width: 31px; height: 14px; line-height: 13px; cursor: move; diff --git a/dist/recline.dataset.min.js b/dist/recline.dataset.min.js new file mode 100644 index 00000000..8ca94447 --- /dev/null +++ b/dist/recline.dataset.min.js @@ -0,0 +1 @@ +this.recline=this.recline||{};this.recline.Model=this.recline.Model||{};(function(my){"use strict";var Deferred=typeof jQuery!=="undefined"&&jQuery.Deferred||_.Deferred;my.Dataset=Backbone.Model.extend({constructor:function Dataset(){Backbone.Model.prototype.constructor.apply(this,arguments)},initialize:function(){var self=this;_.bindAll(this,"query");this.backend=null;if(this.get("backend")){this.backend=this._backendFromString(this.get("backend"))}else{if(this.get("records")){this.backend=recline.Backend.Memory}}this.fields=new my.FieldList;this.records=new my.RecordList;this._changes={deletes:[],updates:[],creates:[]};this.facets=new my.FacetList;this.recordCount=null;this.queryState=new my.Query;this.queryState.bind("change facet:add",function(){self.query()});this._store=this.backend;this._handleResult=this.backend!=null&&_.has(this.backend,"handleQueryResult")?this.backend.handleQueryResult:this._handleQueryResult;if(this.backend==recline.Backend.Memory){this.fetch()}},sync:function(method,model,options){return this.backend.sync(method,model,options)},fetch:function(){var self=this;var dfd=new Deferred;if(this.backend!==recline.Backend.Memory){this.backend.fetch(this.toJSON()).done(handleResults).fail(function(args){dfd.reject(args)})}else{handleResults({records:this.get("records"),fields:this.get("fields"),useMemoryStore:true})}function handleResults(results){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)}self.set(results.metadata);self.fields.reset(out.fields);self.query().done(function(){dfd.resolve(self)}).fail(function(args){dfd.reject(args)})}return dfd.promise()},_normalizeRecordsAndFields:function(records,fields){if(!fields&&records&&records.length>0){if(records[0]instanceof Array){fields=records[0];records=records.slice(1)}else{fields=_.map(_.keys(records[0]),function(key){return{id:key}})}}if(fields&&fields.length>0&&(fields[0]===null||typeof fields[0]!="object")){var seen={};fields=_.map(fields,function(field,index){if(field===null){field=""}else{field=field.toString()}var fieldId=field.replace(/^\s+|\s+$/g,"");if(fieldId===""){fieldId="_noname_";field=fieldId}while(fieldId in seen){seen[field]+=1;fieldId=field+seen[field]}if(!(field in seen)){seen[field]=0}return{id:fieldId}})}if(records&&records.length>0&&records[0]instanceof Array){records=_.map(records,function(doc){var tmp={};_.each(fields,function(field,idx){tmp[field.id]=doc[idx]});return tmp})}return{fields:fields,records:records}},save:function(){var self=this;return this._store.save(this._changes,this.toJSON())},query:function(queryObj){var self=this;var dfd=new Deferred;this.trigger("query:start");if(queryObj){var attributes=queryObj;if(queryObj instanceof my.Query){attributes=queryObj.toJSON()}this.queryState.set(attributes,{silent:true})}var actualQuery=this.queryState.toJSON();this._store.query(actualQuery,this.toJSON()).done(function(queryResult){self._handleResult(queryResult);self.trigger("query:done");dfd.resolve(self.records)}).fail(function(args){self.trigger("query:fail",args);dfd.reject(args)});return dfd.promise()},_handleQueryResult:function(queryResult){var self=this;self.recordCount=queryResult.total;var docs=_.map(queryResult.hits,function(hit){var _doc=new my.Record(hit);_doc.fields=self.fields;_doc.bind("change",function(doc){self._changes.updates.push(doc.toJSON())});_doc.bind("destroy",function(doc){self._changes.deletes.push(doc.toJSON())});return _doc});self.records.reset(docs);if(queryResult.facets){var facets=_.map(queryResult.facets,function(facetResult,facetId){facetResult.id=facetId;return new my.Facet(facetResult)});self.facets.reset(facets)}},toTemplateJSON:function(){var data=this.toJSON();data.recordCount=this.recordCount;data.fields=this.fields.toJSON();return data},getFieldsSummary:function(){var self=this;var query=new my.Query;query.set({size:0});this.fields.each(function(field){query.addFacet(field.id)});var dfd=new Deferred;this._store.query(query.toJSON(),this.toJSON()).done(function(queryResult){if(queryResult.facets){_.each(queryResult.facets,function(facetResult,facetId){facetResult.id=facetId;var facet=new my.Facet(facetResult);self.fields.get(facetId).facets.reset(facet)})}dfd.resolve(queryResult)});return dfd.promise()},recordSummary:function(record){return record.summary()},_backendFromString:function(backendString){var backend=null;if(recline&&recline.Backend){_.each(_.keys(recline.Backend),function(name){if(name.toLowerCase()===backendString.toLowerCase()){backend=recline.Backend[name]}})}return backend}});my.Record=Backbone.Model.extend({constructor:function Record(){Backbone.Model.prototype.constructor.apply(this,arguments)},initialize:function(){_.bindAll(this,"getFieldValue")},getFieldValue:function(field){var val=this.getFieldValueUnrendered(field);if(field&&!_.isUndefined(field.renderer)){val=field.renderer(val,field,this.toJSON())}return val},getFieldValueUnrendered:function(field){if(!field){return""}var val=this.get(field.id);if(field.deriver){val=field.deriver(val,field,this)}return val},summary:function(record){var self=this;var html='
';this.fields.each(function(field){if(field.id!="id"){html+='
'+field.get("label")+": "+self.getFieldValue(field)+"
"}});html+="
";return html},fetch:function(){},save:function(){},destroy:function(){this.trigger("destroy",this)}});my.RecordList=Backbone.Collection.extend({constructor:function RecordList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Record});my.Field=Backbone.Model.extend({constructor:function Field(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:{label:null,type:"string",format:null,is_derived:false},initialize:function(data,options){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){this.set({label:this.id})}if(this.attributes.type.toLowerCase()in this._typeMap){this.attributes.type=this._typeMap[this.attributes.type.toLowerCase()]}if(options){this.renderer=options.renderer;this.deriver=options.deriver}if(!this.renderer){this.renderer=this.defaultRenderers[this.get("type")]}this.facets=new my.FacetList},_typeMap:{text:"string","double":"number","float":"number",numeric:"number","int":"integer",datetime:"date-time",bool:"boolean",timestamp:"date-time",json:"object"},defaultRenderers:{object:function(val,field,doc){return JSON.stringify(val)},geo_point:function(val,field,doc){return JSON.stringify(val)},number:function(val,field,doc){var format=field.get("format");if(format==="percentage"){return val+"%"}return val},string:function(val,field,doc){var format=field.get("format");if(format==="markdown"){if(typeof Showdown!=="undefined"){var showdown=new Showdown.converter;out=showdown.makeHtml(val);return out}else{return val}}else if(format=="plain"){return val}else{if(val&&typeof val==="string"){val=val.replace(/(https?:\/\/[^ ]+)/g,'$1')}return val}}}});my.FieldList=Backbone.Collection.extend({constructor:function FieldList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Field});my.Query=Backbone.Model.extend({constructor:function Query(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:function(){return{size:100,from:0,q:"",facets:{},filters:[]}},_filterTemplates:{term:{type:"term",field:"",term:""},range:{type:"range",from:"",to:""},geo_distance:{type:"geo_distance",distance:10,unit:"km",point:{lon:0,lat:0}}},addFilter:function(filter){var ourfilter=JSON.parse(JSON.stringify(filter));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){var filters=this.get("filters");var idx=-1;_.each(this.get("filters"),function(f,key,list){if(filter.field==f.field){idx=key}});if(idx>=0){filters.splice(idx,1);this.set({filters:filters})}this.addFilter(filter)},updateFilter:function(index,value){},removeFilter:function(filterIndex){var filters=this.get("filters");filters.splice(filterIndex,1);this.set({filters:filters});this.trigger("change")},addFacet:function(fieldId,size,silent){var facets=this.get("facets");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});if(!silent){this.trigger("facet:add",this)}},addHistogramFacet:function(fieldId){var facets=this.get("facets");facets[fieldId]={date_histogram:{field:fieldId,interval:"day"}};this.set({facets:facets},{silent:true});this.trigger("facet:add",this)},removeFacet:function(fieldId){var facets=this.get("facets");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)},refreshFacets:function(){this.trigger("facet:add",this)}});my.Facet=Backbone.Model.extend({constructor:function Facet(){Backbone.Model.prototype.constructor.apply(this,arguments)},defaults:function(){return{_type:"terms",total:0,other:0,missing:0,terms:[]}}});my.FacetList=Backbone.Collection.extend({constructor:function FacetList(){Backbone.Collection.prototype.constructor.apply(this,arguments)},model:my.Facet});my.ObjectState=Backbone.Model.extend({})})(this.recline.Model);this.recline=this.recline||{};this.recline.Backend=this.recline.Backend||{};this.recline.Backend.Memory=this.recline.Backend.Memory||{};(function(my){"use strict";my.__type__="memory";var Deferred=typeof jQuery!=="undefined"&&jQuery.Deferred||_.Deferred;my.Store=function(records,fields){var self=this;this.records=records;this.data=this.records;if(fields){this.fields=fields}else{if(records){this.fields=_.map(records[0],function(value,key){return{id:key,type:"string"}})}}this.update=function(doc){_.each(self.records,function(internalDoc,idx){if(doc.id===internalDoc.id){self.records[idx]=doc}})};this.remove=function(doc){var newdocs=_.reject(self.records,function(internalDoc){return doc.id===internalDoc.id});this.records=newdocs};this.save=function(changes,dataset){var self=this;var dfd=new Deferred;_.each(changes.updates,function(record){self.update(record)});_.each(changes.deletes,function(record){self.remove(record)});dfd.resolve();return dfd.promise()},this.query=function(queryObj){var dfd=new Deferred;var numRows=queryObj.size||this.records.length;var start=queryObj.from||0;var results=this.records;results=this._applyFilters(results,queryObj);results=this._applyFreeTextQuery(results,queryObj);_.each(queryObj.sort,function(sortObj){var fieldName=sortObj.field;results=_.sortBy(results,function(doc){var _out=doc[fieldName];return _out});if(sortObj.order=="desc"){results.reverse()}});var facets=this.computeFacets(results,queryObj);var out={total:results.length,hits:results.slice(start,start+numRows),facets:facets};dfd.resolve(out);return dfd.promise()};this._applyFilters=function(results,queryObj){var filters=queryObj.filters;var filterFunctions={term:term,terms:terms,range:range,geo_distance:geo_distance};var dataParsers={integer:function(e){return parseFloat(e,10)},"float":function(e){return parseFloat(e,10)},number:function(e){return parseFloat(e,10)},string:function(e){return e.toString()},date:function(e){return moment(e).valueOf()},datetime:function(e){return new Date(e).valueOf()}};var keyedFields={};_.each(self.fields,function(field){keyedFields[field.id]=field});function getDataParser(filter){var fieldType=keyedFields[filter.field].type||"string";return dataParsers[fieldType]}return _.filter(results,function(record){var passes=_.map(filters,function(filter){return filterFunctions[filter.type](record,filter)});return _.all(passes,_.identity)});function term(record,filter){var parse=getDataParser(filter);var value=parse(record[filter.field]);var term=parse(filter.term);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 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 from=parse(fromnull?"":filter.from);var to=parse(tonull?"":filter.to);if((!fromnull||!tonull)&&value===""){return false}return(fromnull||value>=from)&&(tonull||value<=to)}function geo_distance(){}};this._applyFreeTextQuery=function(results,queryObj){if(queryObj.q){var terms=queryObj.q.split(" ");var patterns=_.map(terms,function(term){return new RegExp(term.toLowerCase())});results=_.filter(results,function(rawdoc){var matches=true;_.each(patterns,function(pattern){var foundmatch=false;_.each(self.fields,function(field){var value=rawdoc[field.id];if(value!==null&&value!==undefined){value=value.toString()}else{value=""}foundmatch=foundmatch||pattern.test(value.toLowerCase())});matches=matches&&foundmatch});return matches})}return results};this.computeFacets=function(records,queryObj){var facetResults={};if(!queryObj.facets){return facetResults}_.each(queryObj.facets,function(query,facetId){facetResults[facetId]=new recline.Model.Facet({id:facetId}).toJSON();facetResults[facetId].termsall={}});_.each(records,function(doc){_.each(queryObj.facets,function(query,facetId){var fieldId=query.terms.field;var val=doc[fieldId];var tmp=facetResults[facetId];if(val){tmp.termsall[val]=tmp.termsall[val]?tmp.termsall[val]+1:1}else{tmp.missing=tmp.missing+1}})});_.each(queryObj.facets,function(query,facetId){var tmp=facetResults[facetId];var terms=_.map(tmp.termsall,function(count,term){return{term:term,count:count}});tmp.terms=_.sortBy(terms,function(item){return-item.count});tmp.terms=tmp.terms.slice(0,10)});return facetResults}}})(this.recline.Backend.Memory); \ No newline at end of file diff --git a/dist/recline.js b/dist/recline.js index 76334781..1fd8e7eb 100644 --- a/dist/recline.js +++ b/dist/recline.js @@ -1064,7 +1064,7 @@ my.Flot = Backbone.View.extend({ template: ' \
\
\ -
\ +
\

Hey there!

\

There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

\

Please tell us by using the menu on the right and a graph will automatically appear.

\ @@ -1405,30 +1405,34 @@ my.FlotControls = Backbone.View.extend({
\
\
\ - \ -
\ - \ +
\ + \ +
\ + \ +
\
\ - \ -
\ - \ +
\ + \ +
\ + \ +
\
\
\
\
\
\ - \ + \
\ + + diff --git a/docs/src/public/fonts/aller-bold.eot b/docs/src/public/fonts/aller-bold.eot new file mode 100644 index 0000000000000000000000000000000000000000..1b32532a8e40e483069482c3650c3ef22ef16bdd GIT binary patch literal 29804 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 F>VRGqwvGS* literal 0 HcmV?d00001 diff --git a/docs/src/public/fonts/aller-bold.ttf b/docs/src/public/fonts/aller-bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dc4cc9c27a59ab14fbb7444c6ab35c5b33a034f6 GIT binary patch literal 66836 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%0AAIIsziQtyhPht7#*LdNqb{-ye3^( zn|v(~z5EDCKxAF$U0PI5OK>)qCPd*kbqR)Do}lSXCHg?ktoDVhDCaGN)VC)vtMy5< zvs{=FYbE}uaVWFc)?#vSOq<|@xtlx{Bz}oTbfrsV(=0JXHR-B$|GF2&l0%2Y;+NK~ zdnxtXLx)o5UR-ye|Es^;wd?i0{r!7i-?i(PU+q61TeW+*bGXUotZNT18yOpjw#1yS zrkm%jcwkjbdgD>CveUIZR*Fk8$Gv#VT*>%vgmk~wpA0Q9t>bT z-Gedi*D#rG!!Xs4muR!gF7L#!q&qRH(vxgzXsVrRfpB7;wNhwT9BJo;LKkhyFYL?o z_6!ZTc&}6~Rh_rCk8N-BRC$I5!pmp5i@P3}cktIKfk{~}W$nObC589pu&y8Xk7@I{ z;Tf6vZ7ODdg$kN!Oz4$jHJLQYWPvDv;rZU|lBR2E{;H%!cMH7+Ra&r0vwHix>e7}!$^zd7|2JQcAg$P}-T>cAyVhrbDN+mxANWf9-Q59offDI`^CV-Q-h_BrEdSYp=cc z+H0@%Tci5zx+e@T@v{?TSMV7XkTYdQ1=Ng>avlpxP&1|16sQ@^muVi$1y51bD6-M; z*p0otd&k4!@jZRLH;#pq!?D=tXe>4?-q*W#EF2!&+uOT)yrpG)cW>vK;jYe61TvCd z)4qauL{*?w^V}oCi6kr$N?XXViFBN2Ap?m3;&9Ff5^<}}QUaaKg~i92M9wn2ViAh; z;maKkthrSrM?cERXEJVN0i8&|*>%C}NsPVd+(Jmv37#TND5EWW9TV8~m}3zJ2j3hr z?Tg50qT^|2C}~#~E3^fseX%@;(n(`6#k7!jmZJK}jcx=TB?nuZjzp1s)`qDmJzL6X zE3VXX_g^nqZt3rgjmCPS~cMkSkWqf;T1Ve*v zwHUWr!8fN4@(N&Js_Z1%|NCS(Bj^=+a!<)_{yTLZM|?|YVC%-1CFu4YpA$`ay0HCB za3o&Ywd1aFUw3DZ?$+?6+u~T(9SUIfe7C&G5ex<9{>>QZ9`z4{Vk4S?6hb=|#w_Mi z%!XFXBIWEOT!owy|MJVRso02r?gi(tzhC_JEFw^6#T@#+J8_OR!hN04K?nIdDM(4m zNjciBQ3Q{1hbPceaBL;&K>Y+|6Y_x>%y}w>6em!_@gl^TtMNl@3>K-BLp&olcC(O^ za^!87qX@XUPB(_sF+I?a|bxhu=Rj zu-Wd|bn4didT;+0hhvLq+5gaWq4#}8qwdzg=6h$y46jpGGXmEUeMF97^Ti1Jfb`yg z>wHPn#Lx3EO3|Z&O)Rk0EvxPrTpp_&a20L)e7CVM|N8xj&sAPJSLaz@m1}l+y-ssp zkzl`Hr@v-&`*j9+KD-@siNi1r3RfFTwlqpB`_B9+>t zT48bBE}V!yVl)_x=boidl829b>J7yw&K-0H?UjC)v$3)~SSDP*%uBTVgSyT7gP70g z{9Uz%&|CqlJO#!~uOYw=D97+A2*H1C4LKOxa@XYK-CKgeEq6~&-nAw8+o6%sRjWov zhQz`#nLs%G}?`@9G<| z`Yh6Yoc;^HcDe^CDytn^WnYBop|pi*l3m->tCE0_q(r;5FUOQyR#E9j-ZxoGt;Ldy z`^Y-r@{t_Ken`|?5k(+b>1eW!rWeS&Kets|p*{#Gl+b5%$3;)&T=Z_E@orx%Fe3)&f*4r6&E%Y7i4Y?npc8<}3yPKvu`2nk*7 zYv^fv7j#(DOjxTj6B2HVGz{hKh$I!ezb7J%DyG9sSw)DTA&FsYvau=J%G2Zqi>2l` zxp&*wKvkp2Q3*rF%uktfur89YE?POX^)In6E=H}-T!amRBOw(O_XSNozX&_!+)+j8 zU(r;0@XqEY^)JKbAPx>l9zHtj@JEv&`K%)t0gQ>bZPO*3E>HsRrka9d;m{if=Q?s# zEO~Fu82B4`uttc2lZWm1Oi8su!6~VRn4Pr2lH)VvlLT@W7Q#H^)(Cll`QfZE?|fw6 z>T7q`3=XL9HvnMQi3%G>@TWL9#C{h6!0E6ec6#mv061gI6adcN?qa9a_%l%f$14XZ zq?eqlV)BCN;D#KCt4eyACaY9u$SORBm6KH{CKZ*@D5L>|6>3=x2BrpKMLv2)z6u#w zfUaocR`z-)WAW>Q&kwIbyijlc{6;`!jt_SmF%?iGX$gP}kcA7#&_K{3K(uku^w8_d-GdLZLQcne|QX3H4y zlt?B3y@udlwNa_U@RnqAP}s2SJ0zG59EK{92O#obN21y))l(pOMe%f_OC|?ubpkfu zFu>h%*hdHvo)iI4vRH4Nc-=kG5ufn9Ub)Wa-CVxqiu}f4u-G^B_`;4cu6b3=F&kc` z_HWyUG;N*ngI^LRC*4OAXmb!5ddQ;4ZgX?G&E8PbDbIg&I+fn8g}K|5njXn&dNZ2d z(jhs$n665yaZtB}=E>)x6!k<_GMR|Y!lLay<=wy5H!?ipuj>sClzBh8Dlj%WQs?gs zy2DRvH2n|3L1&fI85kN5F0XJqEB$q=KDe5<9gQJYk!e)v?-lIx^IMk|9Bm>!nL}<1r%3=qEvl3UNxDkm|awFX^DHN91 zaX&k<4n(Y&i4J5Lm!Y*r(f}>rGx^VpBh&Oz=U=pMsT0JZ^W*O0zJwxoUm@16laT^OMnnt zoXJZf%o3Np({fqJ*^=^8H$a*$RN$BvY~{2NdaRO@C>Oa%8u}8(+0>SBq zNJ)~Z;5s_2%A!l<2{f_CDg4-I6J8C(zOZZeJu$!V@_f|PL^L!hw1z@a!x_(({(XlJ z@9VwV^FE*~sR`wf@YBxz1Ks`oXf|wZ=$DD|!=0G35mi@8Gj>HZ8y4hnRS=K|=wEPy z_*2de!VF_So2ZPWnL(T~O{gG+9E2zj2n61mDCo5cB8W4jQ$8D+oMrXe^ga5w$o=(e zaD(zWHYk7gkvZLo;qlEVL~u*~LWGbIntM547u6-P_uPT~KJlaa>CAy=C=x%a2TkF7 zVj}n?0-{GkDzKs`dt&yL-swX|PI# zJfp0m6}{RJ?xJ1=VynAjA(0BErR?2Ihe>9$sOr6Zcgd5rXg$m9K7HKb(yU(!Rb|XK zRpk~JbZ_i%KKK>yl`+v;R@YxK+<8M~x8Je;w&xEIEY7jgt=8$>u>s%q>vIb0Mr-nO z-PLg+r?B$X@=t#2edq({Mqv%NAr^4wg~(Ag+*$mWkfR8aLCd{ViU1CBJvm)upp%Yh z(WH1*I+h#0=x0NnI9h7<*Xs3tp_bqrTv_f(*VS!9E-UmOHv>%($i+9GiuGS_8-;eG zWp)=qp%!hKypqbUERB|(+cwWI=$^;WGWM$Mf>hf)=(i?RlUuTFAekiUqmm1zfxVne z1F)WvLtPq4;k_2%wzL>vv}*94v^UB1Wafq|z8l*7`bofJ=-@WmrC)RiQ|XFQ0sx_)CP7C{oVO`69v3 z&S%<-sd7^gt41&+wPU%ZNrns1>BGLXJqW4g6mU^WM#~b4v&h=I$U*Ny9NX;cTFa{D z;qSK=DBbfK%c^_fimE$*FY?;n#ypY_4~t)616eT61VjQR3OJyPpJ0~&iZ`8;Z))-u- z{Nl{W`}2#bUrcN1`Q!NA((FVA)xz3i%>5 zA5wPsbfJho$LOIPM2Rpu36pi&L3tAxM|9?;VRonGvvrYy@>=GO=MRrRKbyLB{rSgm z-^XBM(tXE?;+na09*e-uNCYzSScF<1k^|5N&?x0L%;Z;esdzhqcuBb*07kFSHPi zTDtpZ*ghOJVSII=7So!+z~hSYn1%qmkxduSn&H5jVWKs|1CpYyxWI%+xkl98z#&l` zB!hq^3ms^2uwHE>Ra2t@ju@rV21UCf#)0Dcsbm7;ttA&BhzejJ0xm*c!d$SZTf%_* za!_ZA5D6cBJ-mE!EUdf@QBJ<{@5rJ>ZoDI3_;xliQgFe-ntM~Pi^g=9so;^hy5rF~ zx0)b1_hlSFD(8>L#}l77O&~X*1+_}&Q_?6kx=P-0@dRi@cyRKAyyKe3PNU5ivD2mv zb{^$pw9b@Q)$=B<$87-*>}mCUnGkwqrrQ}!BAED- zl!-KZ|C~sCw|Q-IsoG{wwT&lD$!)WtZBS-9wMezC49fgyk-=H%p^8|P>0gVsIT8E6 zMBD08%_&h&#tYge^rhSL)vW1Rvvm*1O%kRu#!{-^Jm|LoD)S=i8pn``kV~~KitPfB zvvpC)i5a`JjArZ@@5UxH6_wgrl%}@f+N5-2i}kUhQm5N*;B2s5VuLX$3J(KsvE+dV zPxASp#ZK#VfY;67djy*Jve0}=>W`$Qq$wBCxWHhVRb++kPwVys zWYml`;buif;gpQpAR}yvUGmuXleZ;xjwEA>Ll6~0L?{uPw1UvYAw^VCh|1xrZ8E?) zLHl9JjV%C^+Tb;oh2_G@^t8=`gl!CWY@`+liEj(E27Ae(auP}wAM@S|$m)@W{+U@% zdaQ`bJTR*0(=aMdYiN`o+s^tGgR9BBbYLZ|sEjYeaRqdXhulcetr=Sr*@y(o$Y|M2 zUv3NDjN#2#Nb19)O0hPvYIuZ;49%o%Dm`07T#K!+DQUq=yl@4sU!ZG3q1KX{(KO-i zg{!W%_T8mnM$Sf+vHH%!l~`b}2x0yE;wJbhSV`R17Ds(86y2g=XNq&CP-leyHw&-) z;tfwQ7<=Piqd`Y~$s2E2d=5vb;>};g0uG<>^Ecm!27}%=e(nu8nw@XF>1=X18mw>r z(i3!q(>jl}-ivu5LNzxk$$}$*ZhCS^)z5Y%t1zx33I@-qbh1`Fbn)}5mPWVKA7UDa_Z%+x9lcR@Xx<|%mubUKv*$4K) z4&od?taV=40U&JfY9v`CKY7WYNAja3pQl6D(v}znM*DO|2N!CKQ zB;oIN^;&m9+@GN~wc7n(wTbA2Gw#|3-mw+d1V|1SZd2Bgz>;keZp~iVE(9?Y*JYNs zbh|w0t4_*gpWj!A=1#<-QUHrvT>~v{avhm@T}QQIsOzL!Ov7}H=dKHcT1i<^@2Lrj z9ENjK!nt2ZB4C~F4ASZ9g=L_uIv~VX|%v_AIC3!9rq;8v>BkOf4_POr>@*=*pQ@6~&mWyYOaLNLq!-dIQAR zMW6{c_VyQ{B#Yrkw72(53Qp5~uC>pZ$~gBhA4tSW`3 z2#`OgDb5&5UF7_}08&xe0L$JoO}?T%Ys8QhKcBWJhBJc%{xak|Zw8mtg)=44HHi1X zsieu$>IynSD}}Q=H^`szU^Myo%z-Vt300yg{fmrw9M$sUv^qqEA%~7hL9EM?g@=dn zjT?fgO64R)3Y-hb(d6^Da3FkpJ!}0Azn$}pA9`imM*mumcUTbXd=53u_*)cz|1+I` z*YhV%Jh$8b23`uk5_RR>*Q_1c)?KbwVo*uHkActC!#=^Lal8?baTZ?ePXV?P3HR2^ z)wmOSiYmi2OXuXG7E4Y9%B#4jP>UszV@Z@!Kt?^D*Q4|d^cl6Uq+ll+Sw$f#h1N&K zifV5o9q$8h+zlyIq871rI;WJJpX32-Eta8Vl*Uyh%He}*hj+6`bSgP9$;go_hkyS5 zEe~uBbPRNa2FF&m)J+`QJ@T<0(On)I_x28i2GGeyOoTADOt)<=nQ zSkG4s>|}kAS4RZ$%0TH5PET9<1>y93EmS~_fDd0B`Ea?4=s0ze5Jd4uDe~j&J=r|O z`Lg<}{|;GALsqn$#H^2zsA?LxYoHt}5FDfzFD+Gw=tWEGRdp<>`69)o|7RuUboPW= zS}9q*0KIBOWyOyxvRVecs#{#ImS^b|PHcf=NR ziFJgFwHwn}w}sBTQ(|2Uv34uNbs5QVrS)+RCH2L4Eib%Ib&mg?r&8__N|@ z%ED6R+0FK<`bK|L*%qfifR$b8FXMh?4O_)ds%B>G5uskd;GRtO%E#i!%BxiZ9ESKP zL08Rqf^bL><&q-F(OsDpF40duADjJrk)pluPu@uRc39^;vrO)%~$8?)S&(u~a9V+I`C{yLaDw^K83X z@V|<6cE(~|o!l=5o545;22&?EQseBD5_=hsN)5m~EJxLC3jA|LIU0E^LUd#cDRl`* zucLKgJ1oml%v+Go=tbrL8=)xwYBQa`~JVQ8TDY2kS%I8a*BA4WRJqLHJ!7f8) zG-rJButDx-kSD1W7PlF7XPWW8Q31(v>GuwKy2o3i`+GTZdB=lY-6yAZpSq#lB%ZtO z)WN~O>UUJ@)<3dwD%n5y$PJtB+q+z_1iF?xdN(<2C9qlJ05a=_h1h<<%DO<3veT90;wpJR%CG5OHf?uEk*Y)YJvSJ(OL_ zq-6*MnP~{~@Q{VX9p|2t5ZkqK=fRt|^txo43MAE4VZIvrr zMe8T;o(wIHSaz3=EBOh9>a>N|ZCgH(6~iX3gx9}?ZBr48J!HFZIU<)l*(hU+ zd0{&E69O`}IKZ*R0ZN9XO+_wZ#AbjkCJPu#5|aRQq2*|A%1#sQbSr-G0=v zOh)AX$7V^Ol!>U^|54umQR;tUT;ziHLH$n|4w>e+rJG+v&2Lz``Du@l+Csgs9^9p2 zn{K+0KImJrjnZ>3DszjdjEmEdOAHq27;31_V|l~8PqxWIp<16&q_(%L8NGxIA@Jc5 zTGHDO%LRtehESsFETQ=ljoh@C4IWD(m)40m-o_rC5a4kx%Q(q$9I|Z0k@<8{VH@p` zx=}F;hG!y9v6`@pr2bL7qR2u85v~k1q{F8V+{i%H9$<@^NV1T%ZKI_$aKDyu^q?Yp z!Me~cT2wjp4`^6L=XRh|bZa!MA&SBH66Yzuh}`Wm%5{=ycBHw5gbA~gx|Z`YKtRQ# z`g_7+p~?}2O-8|Pq|Hh;1SzDdk|8<-2hxRjgQ(CvI3=|YR|Au;Rn0kH&R~)=g%sv~ zpP?heM+WAz?iA+OeB2{wltx;hC^jYye&+WG7@LTD6jfYjeuLbf4ns?{P8TZOZ6VN^ zoKvmlIICt16}>s2o)@yX&HmIh06DⅆA*wV|2E?UgaO-298Xthc^!eoT=hLnT61q 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 literal 0 HcmV?d00001 diff --git a/docs/src/public/fonts/aller-bold.woff b/docs/src/public/fonts/aller-bold.woff new file mode 100644 index 0000000000000000000000000000000000000000..fa16fd0aba81582de121d833c7bc50e8f1b1b981 GIT binary patch literal 33244 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

--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 q6yXA@F-u0Z~1!1poleYR}~W literal 0 HcmV?d00001 diff --git a/docs/src/public/fonts/aller-light.eot b/docs/src/public/fonts/aller-light.eot new file mode 100644 index 0000000000000000000000000000000000000000..40bd654b5fe63501ae64d5673b5aa45051a69e65 GIT binary patch literal 29509 zcmZ^}V{j#0&^3CJoY>BZZQHhOomdlll8J5GwmF&Dn%J4xHYUm3=Y6a0t-AHq*XzgX zTD_{P|Lh-o_m*-40PI}>0LcFU1pE&}L;WuSApnUGfd4c_)dm2d(0~-6`k(&4kpcja z{!cRJ8*lnQ{QnOefEd8xKUe}>|FbCqYys8)8-V+NCKQ1Bf2uXWmF>+v`kxJnXWaBr!X`7+c#VR9QA4~WS zjkr7owdQTqE9(cV$CK?f?TTq{WVR7X{DQXhE2;qPOL?G0N6(zMUYoLZgukK-+o zzW+IYvFC0HCHT$PyYej!9+$b8=0;LR7V^6~5K|VNOc*K9ws!=`v|dHdimw`~kuyZX z;g;s@?i;j?7g=3FEwo3T^A-e(jU9Zc>XWX+@0db1ACx^gGXBOh(u5Y7bt!wr^=>jA z=>0)5$@ursZM8Q!3yrPgEDu|-wk(zC=YT`3lm0ZWudvM!goERNdM-n6<=ggF@WN&*jn*wHE6sKh26zaoUq5DCn7jb_dB)%Uu#dE9GaO8ia z*)-SuR49M;&0S%8`pS1Wq9bTA0=h=J{rxMPAL&b=yGRI>lqj>!zFb@+Y5x5N-=#LN zbR;Yx(4a`j1(jAejfu^mwa$^rM2*_PN;!oyx7I9*(K9c|PUG5hRSEnuHnZCsbdyNWXqYHu-f<5J6oC}x7U<5}n znRCm@*V)nvizOD(l@Q7D40*w{Gx1=wrIAWD3){dUj^c8(Y3Ha22}WWyo|I_nvol=` zd_K`eQwDowsZMwVmC!pzd5ft~6$L1ADq)$*q=R_`qa-3!b^t@OC}cu4lv}OrR`547 z_6@PsOy0U4J)oEc$tP)x5?sj$`t8=Fn5zth3(Kfu^HO_a*f%m7+bH}~Lga1Q1sc!@ zWdUEL4cvv7cn`{jYEh{vj&LmGXDeJSEl}2;9HXfUXm9LFrcO&U`8}G3$vJNUBA@Xkl`Z5vv0F$8FbK3kZsKLpn zEc~&~zT8Kc3;e53@4fzE2uQXJj;c&0E-qUrZ`7LL?kV)S1ndX5V>qM+UiPvXtw;J1 zvFky#G=_E$qZv#VF<`aQSR;jw@a1DiklXsU+T=d>s(){=Hcjm-lOR&b{KrWK*?8}{ z^V0sTy~x%Dhl7Z3O%KAB=WgetF;LHpLh0OyxdkIll zmxG9q&vr(oS4kCxdL5Rq(Q+dy5Q=Xn5DmKdte^^=ehr@-9sMwQSzhSPI5-1raqH|& zcA%bhde@c|bF5|sQsv7u3%RTy^HwrC3pPP(ZlXjd@YxrZbLKPm?(P2h&LqV1O=jN# z8ja$vT`cGeNdUK_>|4=%-$b>;&BPwn*x@t|x0pt<%djPff##qa5zgbVLNhw^FuBpR zcVa1}(^*Dn9JEM@`~|M`a5sliD~?>V{`cT9xdIk!>zIBv3b6>-d#)&)hZNUd9;U&2 zv$56kAoEX(;zxYk!a>YB(@cm+-R-g8`Sa?+VSLfl?3C8Ws6{y4Cp3msJB?z`r3;@N zJg1A$3~6S|!qWIKay;^^+4me2YNuKV<;e0onWgZkTnqSA$wW`;AX<3_l*FSYetPmu zC(XTWjpfp4GaK@pX1Itb+6ad62nSqF*^_zS2C0Xhgx0H&)piu=HG!Z(i3M)$lh5LV zU>=cwTb;35hu@GuJhzcdg>$7Qb*KOc(>qiyjTs5h$BY~b2hBy~S z(DVc3QT&(;hNcRQ!}2WJ74?CIvvz$b&dQ~Sdk|CWPy89BHfyB^L`o2mcUBrD;OVnX z*3s_s&zR=Ni>TK>U|$6(nsEIxF@PXUU(A)?CX}Bfly7Z%MmkdVJW}5GLc~3rQa3t~ z+i`>6zJT9pUZ6EwV3109(!E(X4+nV~o}%|M^@DMDi*CA~)*fuP{4klA00%UXPM^J~+p8Jry1FvLc{? z|E=y*v1imBbG8#)zNk1d??o`&R2D2E>x;ZQvApx4ZXKv66}|bFb|5z`j5L3)d2)R3 zv@ZdMk9A@-EV=}5G_9oSD0xI8Ewgm&7kPXVmTzRq+~qqz)#_Zu5|YM+6G6Cl#fl^Q zVVH2S>F;_)91D{`WMo;0!dCz%Seo^7ha3QIgfz|-9ck$e-V%Z?3p?+FP<%zO?>9+% zcoSz%cVqb+U%r-}brtYpT+PuV+$=d^vNPChYd~!4wR$eK+g9}`=P+A@hqBA)!1ih# z_1>;e*^~R?#imM4wTX_Qa6_lRbnPqBH4Wyo zbHY~FPn1#Kptb!x^GG*BDayRi8phwjTv@;xeQIT8p}YXySZ)fFKJ%8SpaTLMx~S{y zD*9Oc7IJ2gI{{o1^AuBUGP;3kY3^KU_bh!4ptIn?nixZQ}7K96v1p=^Z6-yOTKFarIU4T`9kP!#>pX#5MLUI{v}Pkt)eos!6Y_lh#fS7?G*qPO!7cOqhjp-RIFI0p14@jH~A; zSOr8sXRv;)$A@}((Zkze+NQq~u@5boN~Op?S2(XN_(MS7zp`JhkCXn1W98CO=Ramt zsYJD=mt5z|s4Oez&%U78 zO)H3eY;&*9S62RbJ;)mmHPR(f;;dJqFV;Qul+)?ruAm;)aG*omslno16X>S2#qzfJ z!m+~nK~Te;*XXp*4G+|8*i;K#qAL>Jz>6!2rWOxX45y$JkPLwbX~Rvf%tVHmlZ6G9 zur%688-A?2yJX5I)61D&+*L7ooXICmICCcUo{w`>7UV&jju>KM+fIv#bL@^cs*(3s z#*ITddQ;i3uTyr)Mvo%QAxyTUhz4_yXS?j#5jAIsuMh@F(DNSpR`OPy7@3JxqJX1_ zd@4A#3E73v<>rktb)&%&dRxHPG$rL=XY0}&JGu7K97SXW&3<0XdUAFsViL0TmyJfo%jnd32y=WL4VnR|7i#hSh@UwShL<^^)zd9Vvzf zOaI<5U`Z4S2$*}6qbG%d1URnf#*HLy=^>BkZuuc5BoL0Pp2PzW4*=sM2z>gg$DU*5d#CuI~00|*BWc)e+9N^xYWNiufwM^q^Argk~$ zapEI;k=()P zP)gnad9?Q`IW14P_9SBaicN0*+=!F@Y6OhGP}=yqM;7j7WBReno>qEgP6CqNGeN(2 z)j4&T9jJO@{8d=gfX&`6R(naR@%LS{x5nACD8-2?lM5VZ4N~-HzPXIKa|6csht3DR zX>70xOeeJR3q?}c2&TGl^w?yoB^=t2G)`Q9{^;v<+E6(kR|C`1Dd<6gG)(X?uFJ7l zli%f7jxgIqpWJJ#2k4#anq#Ryd0PS4yn;3l5J%T+oxf2XK>|f{@DL?Y&l-9^O2S9v zFZID_@|y486I$LcMpdf>EipwEjXBV?NR1A7qf-jyOMwwd+;J-6&-Gz6oNg5OO<)@= zHsxR!4S-mqGClQiG^R! zWptA`GvstUaX1Le)3XDw4{tg;O4Hv``oGc60Y-ZA`kQ7IpW)Manv^=EyW&US9iQ7* zvt(54r+-0D%HYG1!=HU?%eLi2Ye^V6p&QRYbDU(Ag2RB137y|YaBKk)dPQDblmSCB zDRpf#D576WJFrg7MxgMp+eG#4U2Bd`bIDnh`+2b6M$%97UcAdFnlc5t)D(zX_5Ugg zKxy?ghArs*746J3FJVTD#p`aRlw{5Bv$!T*6D@?9@9unMv%qaKw^bd12zQA^G-F|| z2Dm(jMNx&FAx7zdoycy*Q%yNHJwha7bykk}kwL$R$dptNd_}N$E3-4zRMp^Bv03N? z(1kE{{tDawt=4iyS>WjKqY(38I{@^mEfMPnyx8@Zy$*-wb_jMI%g52H~2T zA6oxqFeaR*#L7a#rYI!;QqgJxUxufC^sd2x;Te*apr^60( zy$TXhwb&TRIBZlX!=EmzB>HXQ#Z@7_ZR!S5wJYXE;%6nNXCM&iJz{{1=gTW_x#!U0 zxEea8Yl~nQGNmDe%I&NEt#99C9MM>?pOdvxx3PH>We@Y%_ESifRwew7O-o|9w=H1lB zOpS}noU2MoLm?<7z=8+Cd?_9B-E5j_AQ!8s_R;m{tvRxxzbc zF>RzSDm>Jm{NT&eG)GVkilv7uGQG}608z7#Y4{gg7M+$3F%iy31ptpE{IPU$m83)y1Q2Aunl03>oG6i%_gZ z=0V9j!!ef3m3=1)dI*D2@YHh9dKNTlF*b-r zVDd@G11!oxJIJ&2z#7+;STh!cOsQt&^7P2`dURHp->Fe{g+HV)V*nDr$Ju&1#KM@8P-N-zMd4Hx2GY9!XNHI7zQ|8k7On{W{wHJ4x==(4KJF;oY6mnE$tPvM? zChZ8E!7nhwgO(4lIV-2d!2v5sre6H*6QHC~d*<~(0V+#>K}Am)0Bsq37^X%-A%#7O zO|`N)mXW5E-k+GEiKS?Zu+(%DuRv(*_#{Bj@&`UUCDd+1iL##c&PL>5bLNG&>HOF-Mdm(s5u zVRcUSo>@-*T{Uq~z!b@$JUmkpD<#81Cyx`>kO&|qd|4q8jTy*&=nmW>i-C|&8F-~E?pi)F`DAIL?#O*co-jh-!pxhp7JLu25!}P?ToPp zWX;p&L8)qVj^k0(859JW!h&3Frs1Kt-FXh!c4bTDSkh2|%>hr5g^U)wSoE^2DzGogwWedpv(@E+Mx0^tiN_m6{!ry|2l5_H4X-4Bzx@9uU!d0NkL)ZCKLbO{H=&66fNs77kr!n_R*^OJd+G4fg?r$*5+NDgF@nYm>k|-#SQ&kfKd~Iy>1v2DyS^(V&Aq4o3_j?| zxoMjsH8>d3>unTD|6Z$H{4Z*Q!svt}FTg#iezVn&oE3QGB3D_qvvNKPgYpvp3inj) zFA4zk%t1bU<|CQlvc~6&yL?L3hlKK)>a4${LhsM*Wy|(C1-e}(bd%yh_yC|J!m2Y9 ziBH~s;Na-dznw=m<8wAUb}hi2L*%eWo=)0Z(DRZ(^cUf&f*=Pb7@aen@7W-Zax~>O zOIj8e0I@Im+#z$CyIjNQusz72JpdFpE`1mk*_XPerj<9_QN;~%n%7i0FH2%lTjBce zlhKTbtaFHS3y$=s6krO3V6%^Of!T z14gZ>tWV1|Q2Ix-LmbmI+1-6vj}Rty>j)s+Z-l^}4$j z)2*>lZ`Ka5UW!=_>W}Gp3}{L#f-J6nr4WQpJlJ7oZt|u$3fBux<%l(7pd!KkW^$~T zuN?zH-3-F40Avsa9X@E}`maOiO4|(3w8#Q>w>SjvF22=8J$qO@JTIX*Nftj)Tlr9V zl@gFdQEEgd?PR+0e8?RQ0|AhrH^x=Y7s*6BBG%~nVK@htC^Z)yDJtplK=3xIi8h7% zB^2cT{GGzO2vRbtV_?k(ANMT#l!n6WD6e>flt0e{K1A~1-5U#;_^*1DAb}&;m z{zOphAlrfZujn-ZdD(dWFYvMRP~K6rUOEBoo~HraI%zi^1f={Hu=|N9h4vJ&Cc}m#=*3+j` zR9*`8u_ku;>9F+vrR(~k*3 zzudya>*V3BspY&f?EcS{T{{OI?A>!lXXj+@;~zD-@fsgf@gd`=3UWRG4DOq49gm>i zbz{PO8%o>#TIFCnbJBO*Mj`bvd116B&*}g~E{!9lR4RE?d%8w`+HQEqIxnl6Qgs{X zQ$0isq=A`ZugY6pv>S6#oQKhMUMPKjtR1+|yRTJ!Tj?+w+HUsq84gjCyh&_*p(ZrW zLKaak`jP=v9Ok&qij#?}-c?Gek452#ON)nMoS4}tMK~u;+v=BSt@U025r0$TYq;i? znFOZ#wGjLoZDJ|hn`cfNkl>U7lP8qJ+J2ST;O}?B;^m3VD zoBdU5&!{GWswx~xlXN7<_+zd#8~I)s{-?V7<^y-sQW&4Pm)ACXKLu8R}z#Qt2VlFNsi8pC!B9D zcT69XXXb6uYhgwLsMSeErHK0OPMx1Ib4%?s9zqWPJ-iYN^&~%56YMkAq}9iHaLjta z8k-^2vm2KxK4K>{vN4tp;fSu3hpTdvhW6kVcD>qFD za+MQgJ$8%FJfMcfC4h&y-jVYRU+EwE4z&&2`n;)~t-}v|^F>t}))8`I3~Li4?FfS4 zDIyxHL@U?U&ktMDlk&O1yoNgW$UZj!7WYR5rH}L)Uo%<}M-F4rro^L3nMk9aGN4y( zS@1i58eTMlDG3`blKF(=fj3ZTZG8$fK;x3pR9g@95 zwT*P>LgXUnyp^(YxpBs=Uugpx)*tP>?i~elq%!^E(F_@6vAz^4-3W1p)ll=G?MqPi zmF~zf(?Yht>aR91%PpdoZIhE3uT$4JGL`PU?juRjBWlE=z_Z&JmMIv=CdyuZN+3|1 z$4}aBEL;dnmmeA}UJHLM0?C^y4Q32(cjoP z$|OSXeKGJ6Csl@F572+Q$F@lfu$eflIEEu%(c$1l-e&_m;ilAyi8^l+3(P16e`h4g zu=+=xHa2l70(Zp3(NEuypxD;qni^*islKciyUfnb^M$B&s?ULc4{x)Qc$GzW(O+^7 z+VUV63GHf9YsfKJYz~Ujz@{O#_%wf{bITtdsmNi@ZamNSP>gNw{5BryBqZuN{}9Tn znXYWY(ddhjpbrjo)9YR4*Z)SHQ?ohm39OIkBt)uCTfj|$MvwlPg#k{0*iDlHZ55VSq+8BQuhX% z7FMWoZi}Mk+_*D`H{Z^-D!IZi(F8VG7mYX%>hc4Qpm+;MJLE82U3SKs$EFZBK+p@O ztnA@~ep>(-XPFfuRZtU?%fa0>uW6iaA(SyoDXD47#+llUocQ-F`fV+$)k|R~sMx$B zO=iz-2KpEwp2GeWlECaJN6aNx;H$($LA zM@fk}wGshO7`%uh(xQql?yI?#nAHpkbv8`AN)~dQC-65$nK)-2Cwe0Wo~R8gy{^9I zDVo?;v6y*;VMeKNrrGSZ(sm!z1Trxa>JZnuV(&YMfEkHmU^>+5&3Cs*({R2T4hmBW zpC)FiW=+EI8}B1dVfMw_&K(6Z9xosvGDrU0xP)V(99Z`v8-GjkQcrU6;Z&*y;lFHj zZ1vrxyN?L_O$vs306Ai*<5kQUOTn{n^Bp*4tji`(p=$#HD#jYBq`kpw^iuf~BQ;nk zZ8X;AwG#cTc{}-O2brcWA?E+^MbSB?SU`f3nT&9KZo%2IshbitKV?J1VM0e|EhxAp^Q3H?7E1)_X+JoLfLB6M#8%G_!09*)oi`kklOxdQ2<`#)j}i zJ={uJE(=dIEKB}7cjpN6ny*?i#lMau1A`O}$iMWMvgt_bhHFfo^U|SG!MFor0=r{x zvX(6tg;|x_!9xh;mzoXomvqb{{fE3s+%mia7tylWr*4N@_*_4$#3(N+H18pXUB2S6 zSfgA~0e890i(jo+DYHe#mLt2~NSJpcdK76nz*!bdvmprfLF0BpeBpxVbVdYR{&m}G z(lE->$Leg+6$;fxkHj-5xHtq-2sW47Ea(ez0gb$LTd65n%e%;Wy%2~*0Nh5Q9N=io z8<5!NE)0_0L}s-l1Xv6t#^HT%6)FbvMH?whpQ3>m(2{lZafO+LQ*lp}6v07;Xw$)E z`O)PA`|uxNECZyIo;IbUWQayyyM?!+^<8^Pt7c^2`pB~KEGMtBQiOGarZ)LTX!UQ; z31ASg-Xz#M*JqB8esXV*cx#rXDsPBYsS}c@dbcTwmE_xA*XkXb8`-}x#Qs1tx&=l~ z2VD;5#WrcdQE1MEvjXdVKEb7AncXZ;kN>Q z(1`uvZXqGlH{~WDu-Ghlc(zTn^`r?!n)q&oaH=VIOQ2d&S%t3uk?+Tu-R(fWr?jhs z@O6>(Hqnk=Oc8kWzs1#^KOTr61FS|He52Gq1iL$ft$!AE%V^i=^i!>YUF$T9)lD_l zp@t&DU^t{wi{K;Md`j>aFLrq5T~u~}H`a33Hn~`xMJI(~M;k6c>e)Q`ixM0Nprye^ zyko6%B4ea3IZ6E*Ec9jn^?NjC2);f}4ZPhsUfct-8&wfKIsG<+Uh-pJQlbho1BrJ5 z^gO#;!Vc@8Be|j(_Wry^<}tx7R>fPQel(U;?t*h2nZ_@l$}s2gW*yN0~7U9FwmVp4Ej%B+7x3P5++26Z_%a zVG`0J*&Y@vg#2JUU$M{^!VAEvak*2JWl|-VO(_52fn6S!jirlX7LUeXQcvpW`&|f~ zTlbnz<{qxWXoK%U^o=%X{7XdE)nKEqLWApvR?HLjC8e#UJZBx9CiM;Fi@K?_%-JxU z3d`oV`r`L4o)HQB=a}63jdqNCYkh`Mo1OtwR}$Z$`=0jgzch+8ymsXUarkVLJVwVn z26fFhEpdElU$QHyvKZw1JcMytylfKg^@T~EPJ!7!mwvK9dD61EP1{T6}aG;%9Y<*_G%she2$)zk*)s1 zS||&UdKr(lnb`VqQ6ib(|K{dTdJEBuw#DBe86Ds>HWOThTo{ zw-gV@1NJckI8Zp|8^-rm#`5teR^cOgPSKS|BuQv4w96&z-NSZe`=&5@;zHL@-B)dor|LbG5|A~{elgj8+Y^@Oy<+hze)<} zZ>wfOxh*ZvllNw;kMwesnhu4KVs#BSdm0chA1p0~=mNYO`cl1JVdjvb4kdE^=5-N> z2PLx8)_6x@P#cx-Z67$!^NLAJN=n*+N_;R}I@@7a?+t_5Rbox*226nJ$qs9(wtK}hRGCNXY(KIA8-hJZ zJtHaIGEwC5Z*vuj#>agyi8C>w3L4XLT}wFQ(8+gJ#F9>V{Lbz6bfBZs4k~&|T#YP) z2Wc{PdxunThPu@n-fbMHYO!0ETJ(*2X z3kQ^0x40(0kYV(dCmY>wWmSVowI2hqtR7Aw3L}+YG~+Y)rzk|}$|H}8a^SoAa9mP= zgO=MxpC~u0us75|BqrK6^e$UYMCuIQxmEKkr63!)&GjfJ_9?cV@cw@g+&?)Sp2#BSZv!TY&PDI|9ZLkKo?JmSz zot_MwIBANT{9cz|jH#G=B_7R787HEW%!e7!tMoKBD(IS7_(H=!fZgRaHR2ZGJk(1;pixbn;5B z>ZUt%YwgBPqV%LAMYE9maZ`bl!|MZZWDtm{!2j-c7m05O?Yuo*_XGM^m@k|2j4Vha zTA|id6I!fSbq~@2=%lNFUn_d9Kfy0tA(TL~)?zY=d8Rq=N~EvYaws!7+>HFZDMzB) z5}^f;Y2y&uh{+h{fTauv+a5$V-SmSX)b$5+TFsX9a|&Kj&m!q1@_k}W6mbi0d-Rh@ zej2>3WGqHSjUmE{T*kGH?dOVib0d7CL2G3}E(!}lO_hEA2eg1xas;}mlP4+1FERsH zao9}!eF;REVkjQ3c3p++@zhw5f|(@EBJA|it2{0M{nI2$`&wOuP)Ql0B}Df_+2xIh z-dwxLW{!h2*0xFhZNJn^ZdY)0@22>QQ485-60!kGnyNT&)-!qaxQ@zKP`B))ul9vO z4_~LB;f$WSR+m`XNC%*UEmiWqfrIUO%6P19m-X8<>_k>tYW8`6UStiaA~LuknpnMa z0u&Ez^H4WP`6qp3H{Hg-5r1r;8(#-)%Nx5vXAvKcl90JWAt}B+xuY5bHl{S7-P!nxBj0uPDVQKEFlv(IIZ|NaNU zq{4@f=_+BJ4s;VE|oiToI=qrT_JWWwa=nFa)FGCT{!++^(iMVEOhHwpxl}=1UZuiy?$uOYF zuXI$qVS>5modVZ@phELh;iB-oL8$fW%@PWPK4LtTuyPvwuq@G_IpbDQWXj#&`7>yC zPapLE?g(<04kJNy{G=aos z+OGGMR5&aYlzu*Q`BU{LdA;{{YJ{K4$-l@Ssx1=^Z5cGK0=?VKez^Kd!u=?Qlqrv? zA0!5+JL9NAiCRK8A-F=A6I+$uadhf4!C}Fx%LjjFIF&$A>neFtNcH$Mz7 zRv2ccpnjU&ym5? zHsmcCy3zCFayrEF%mlRw>!UTQXLfvBS5TA4_PYDo^4e z50`6cu>JyOZI7l2XvQ<0V`#DFNk9(XDJFKgVy~CbhlO znz-Brmp8AOAaqZQguS^?Or}3m?)!&g_UD10lSeVzZGyY=_wlh!c}Bcu55F*?%XPUB z`oCpsx+rGy^R>B^FUB1a0M3ada4TaWLl>SYgoR0s(rq`z&~HqG>JWc{3q;~v71?eP z&oUu02}lWbyBl7;QX2#l_$~1thVj!?`g-3{2S5kjZ#f)qPv;suD3rm6T9FD-6^q|C zL$nUaOjfL}#r7&7{tRP^v)L5sk!jizB{gMZ)cy2h1cDqt^x^U>4Dzu%)ADb5)HccY zugN^6$OBzV5N}Dolv4hvRY;NK4^tIG>LVP;Vvg58M90YL&xc!=Lcx(94;~uU5>mCkvNPDHHXku@^JW%_^e#yoK^Y@Vqmh39 zegcC)jQT{{$$O|SjgDP3LnNWlxO>bZvHgALDUhEknPTnGmIUc9=q`gQ>L`be2aPPMFS@z zSguvc@uJpSL@zzS4>e+<`n&qSp_p5OC$fVJL&4#msANn3cBB(kZT{uy4RMccHw=PvyN3)Jd2S-Np~ z(Sa^ce^CY(*H?8v?c%R#9oSjXwq>YbYv5ybZE}lV@3b1%<)bdiJOjp>D5MT#||H5#*LNogU zXzY)ESU@)gxIWxy*xn}Uq7a?CCRQuYYAKml*-i%7EFg?u@2)pnsrNez{wQG=hzHflM4=4qzKiB z<(2d+ta?1oxW?!X)`P*bC{rA?EaAn^tIt8-uUt}71>iPII4C4OV?6+kT`dG1DXB2kK&zeknb-1sCr4OZK zmnw_K<#8zwt6=V1(Kp>qOxk#iFhB}1l;U0$8LAPXKwJ0jar(V-iw})OY~Y(k&3w>w z=(Ul`!+!=GWQI+a2H&6E8*3K zoa6ojss57!g`-joSu?OoKOSZ=sF4b#w~sW0c_PachA6e&1_Up6RCWMIofkM40K$RR zk`?c#Du_WKQD{xa_L+6paSv`OP}D*dv4#5it9{o3BvX)GCBHDL?qeth^uki7%`HoNAJYfDk)dux_XW zZo+*^tna416OhAohA(AFFf?B>Dp`H&)<~vhD5%6en&`K+5grDeWD}Ncllj}Iw48Bj zBdBCGKRQss3;TLdA(Xv#z79ikB-~z#3WBu|3e_oTac~e=_(+vf;3S(Ynhz)3PnR0s z^I@vCN0qbEJU z++vw7Fy$o__Ghh81nBDJw-yg%*N)2ls}%B~d(L^H5Oy)lBD*v$6BQniVr7?{S+W4h z+LY+EEe~K}6JV)DOIKEu*PA>#=t$w7Lv=wARw~38ZOp4Ap4eRfha#JM>Ge#OQHJ1D zo4Z|Cs5RO9wk~=MPW#Z!?vEa?r9Bb2!g6bk;ljvdmJElZpvqE5Z#~{QphUkwz;|4Q zd$e)38w~UA$!HsFzBtn*SEbT<8pC~s{1SsiJ~??krUfK3S05f0DK$>8gLWOib9*&c zEv2jO21v7y%dkTdVtTs3OZ^5H=#jdtA9g}5S=pIx?;?WsOXAlsaM5}b>vm_9LLD-N813XhpbH5m{9#)0zXzZ+y@_Q|HA;Gbmv5`eMk4O3Ngo;3v|nd7os zX$OhRCKF!;8~Ay5K3Ga=Xk$k}L(_Tp75YpS^iCN=@B6V#kCBq&Z4r&}@ zE=<(eUo*V?)$*ynDYNr(3?=52y{&~3XEQ5i1LTD#@1G%L*ibsOvDHa$8G(vbDuzU|zg~zL z2#^+h>55*b{nB;kC;Nig)Fxt4Wo~8dn4p3ENaJ&ZS1yt{sKcRgrl~i{7Cljn^y1qq zYm8K+m5r3r5|6J7Y06=yzj1FL1SmiB0`p@(dp6SOu3PM%li?joF`&K){`EA{B5@rs zmantL5=oF~iel~6CnJJ*+PkB`0vgn+H~I<^L+T=Z zu~F~{#S$j;1(n(tMq4%Gd>{?v&FvemXatpJL+x*zPqzyZ zIK#(lvxG%LTf%*;&$Dw2Bi}Y?(w(-bDQb}GDm&>6*CSd8gYxM||KPom$YT*9WJBUc z{v(mAV*b~eI_=F_@VGD%)k95PW;`2+^#Lf3 z-{FVS9RI9kVhh~g%A^!b1x^rC3r3#Q;0QsW8Xj{_p!qg+1t$gS33h%GlN=DLpA%Vt z%Nv`Lm6$BgYcyFoQ#bVeBE3lYze(=2N{jsy@s<~bGfE#gO8??qXiu<(00<-xYJAI8 zgcDDp>i_ieSp3x)DbEiD(B3aP)-rNRJ|y4 zG(5hmIfPl$S*qjrO=#5x^0LmR&P?Lu1>G=_b-a|ki0X(b>*+rnMd+$SE87vcY2l4)X0g-y)>VAB95PL~aZpkXKL>|FZ|t7%A+=%CgG+?bTok2jJ{j5d*CE~OpinBK6|8p0f`Esl4n!2@O}zZE zn62$U7}S={P7W>FpkxN^{CMM9OtOzY6Dhc`KpmC?W>dPtLeIO&KMp}OvBAs#iZcJP zS=t{LZo}KK^cpQ*Ij2FkKH`^Pz0x~}PCr3f(?K)Qfu)u>>HnS+be1ax)H9@7fjuRp zI;yawvA?~iFVizW!ql3B{iQVTPu7;D*SO5r;zltmi&bPH@`cXn(Zq1@`zmX)mj+g} zA!lV-4SW2+LCRka9?#^)PNw?k0T_D&1d?0&uoL~laq_46RYt#P*$vo~U##CT5kCm* zqc8!k<4yt0)Zy%0{)gS0N{wC)w>y8|^pn{L=cZ(Hxfe|z|4j9*9zqkbRmNmYwM&e8 zBc$3kE_%VCU)J;o zW)_NZ0|vDi$!(7BU?(}sMiTTE<86wWi_WE8 z2jrQw@|22)zt}owsakKC2tQSiK869iwYVuEo1U1q@qWY1A�y6>nTFX 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 literal 0 HcmV?d00001 diff --git a/docs/src/public/fonts/roboto-black.eot b/docs/src/public/fonts/roboto-black.eot new file mode 100755 index 0000000000000000000000000000000000000000..571ed49125910cd6b6b5a9259134dd2e8d37766e GIT binary patch literal 20702 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
~sO?6glRGZi8n^KyFR=i1erVaJp>l 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

  • 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 X3&Ic#E%XNK++i8SYTo{GJ<-1bF1c8o literal 0 HcmV?d00001 diff --git a/docs/src/public/fonts/roboto-black.woff b/docs/src/public/fonts/roboto-black.woff new file mode 100755 index 0000000000000000000000000000000000000000..642e5b60f7c662ab5bcf67cfc1089f50bfdd7b00 GIT binary patch literal 24536 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‚ZNmb*+jwao2 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 E0ADU%yZ`_I literal 0 HcmV?d00001 diff --git a/docs/src/public/stylesheets/normalize.css b/docs/src/public/stylesheets/normalize.css new file mode 100644 index 00000000..73abb76f --- /dev/null +++ b/docs/src/public/stylesheets/normalize.css @@ -0,0 +1,375 @@ +/*! normalize.css v2.0.1 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects `inline-block` display not defined in IE 8/9. + */ + +audio, +canvas, +video { + display: inline-block; +} + +/* + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/* + * Addresses styling for `hidden` attribute not present in IE 8/9. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* + * 1. Sets default font family to sans-serif. + * 2. Prevents iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -ms-text-size-adjust: 100%; /* 2 */ +} + +/* + * Removes default margin. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/* + * Addresses `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/* + * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, + * Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; +} + +/* + * Addresses styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/* + * Addresses styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + + +/* + * Corrects font family set oddly in Safari 5 and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * Sets consistent quote types. + */ + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +/* + * Addresses inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/* + * Prevents `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/* + * Removes border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/* + * Corrects overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/* + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Corrects font family not being inherited in all browsers. + * 2. Corrects font size not being inherited in all browsers. + * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome + */ + +button, +input, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 2 */ + margin: 0; /* 3 */ +} + +/* + * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/* + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Corrects inability to style clickable `input` types in iOS. + * 3. Improves usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/* + * Re-set default cursor for disabled elements. + */ + +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to `content-box` in IE 8/9. + * 2. Removes excess padding in IE 8/9. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE 8/9. + * 2. Improves readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/docs/src/view.flot.html b/docs/src/view.flot.html index 08a8a030..30e442fc 100644 --- a/docs/src/view.flot.html +++ b/docs/src/view.flot.html @@ -1,476 +1,865 @@ - view.flot.js

    view.flot.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  view.flot.js
    +  
    +  
    +  
    +
    +
    +  

    Graph view for a Dataset using Flot graphing library.

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +(function($, my) { + "use strict"; + + + + +
  • +
    + +
    + +
    +

    Graph view for a Dataset using Flot graphing library.

    Initialization arguments (in a hash in first parameter):

    -
    • model: recline.Model.Dataset
    • state: (optional) configuration hash of form:

      +
       {
      +   group: {column name for x-axis},
      +   series: [{column name for series A}, {column name series B}, ... ],
      +   // options are: lines, points, lines-and-points, bars, columns
      +   graphType: 'lines',
      +   graphOptions: {custom [flot options]}
      + }
      +
    • +
    +

    NB: should not provide an el argument to the view but must let the view +generate the element itself (you can then append view.el to the DOM.

    -

    { - group: {column name for x-axis}, - series: [{column name for series A}, {column name series B}, ... ], - // options are: lines, points, lines-and-points, bars, columns - graphType: 'lines', - graphOptions: {custom [flot options]} - }

  • + + +
    my.Flot = Backbone.View.extend({
    +  template: ' \
    +    <div class="recline-flot"> \
    +      <div class="panel graph" style="display: block;"> \
    +        <div class="js-temp-notice alert alert-warning alert-block"> \
    +          <h3 class="alert-heading">Hey there!</h3> \
    +          <p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
    +          <p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
    +        </div> \
    +      </div> \
    +    </div> \
    +',
    +
    +  initialize: function(options) {
    +    var self = this;
    +    this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
    +
    +    _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel');
    +    this.needToRedraw = false;
    +    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'
    +      },
    +      options.state
    +    );
    +    this.state = new recline.Model.ObjectState(stateData);
    +    this.previousTooltipPoint = {x: null, y: null};
    +    this.editor = new my.FlotControls({
    +      model: this.model,
    +      state: this.state.toJSON()
    +    });
    +    this.listenTo(this.editor.state, 'change', function() {
    +      self.state.set(self.editor.state.toJSON());
    +      self.redraw();
    +    });
    +    this.elSidebar = this.editor.$el;
    +  },
    +
    +  render: function() {
    +    var self = this;
    +    var tmplData = this.model.toTemplateJSON();
    +    var htmls = Mustache.render(this.template, tmplData);
    +    this.$el.html(htmls);
    +    this.$graph = this.$el.find('.panel.graph');
    +    this.$graph.on("plothover", this._toolTip);
    +    return this;
    +  },
    +
    +  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’
    -

    NB: should not provide an el argument to the view but must let the view -generate the element itself (you can then append view.el to the DOM.

  • my.Flot = Backbone.View.extend({
    -  template: ' \
    -    <div class="recline-flot"> \
    -      <div class="panel graph" style="display: block;"> \
    -        <div class="js-temp-notice alert alert-block"> \
    -          <h3 class="alert-heading">Hey there!</h3> \
    -          <p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
    -          <p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
    -        </div> \
    -      </div> \
    -    </div> \
    -',
    +            
    + +
        var areWeVisible = !jQuery.expr.filters.hidden(this.el);
    +    if ((!areWeVisible || this.model.records.length === 0)) {
    +      this.needToRedraw = true;
    +      return;
    +    }
    + + + + +
  • +
    + +
    + +
    +

    check we have something to plot

    - initialize: function(options) { - var self = this; - this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; +
    + +
        if (this.state.get('group') && this.state.get('series')) {
    +      var series = this.createSeries();
    +      var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length);
    +      this.plot = $.plot(this.$graph, series, options);
    +    }
    +  },
     
    -    _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel');
    -    this.needToRedraw = false;
    -    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'
    -      },
    -      options.state
    -    );
    -    this.state = new recline.Model.ObjectState(stateData);
    -    this.previousTooltipPoint = {x: null, y: null};
    -    this.editor = new my.FlotControls({
    -      model: this.model,
    -      state: this.state.toJSON()
    -    });
    -    this.listenTo(this.editor.state, 'change', function() {
    -      self.state.set(self.editor.state.toJSON());
    -      self.redraw();
    -    });
    -    this.elSidebar = this.editor.$el;
    -  },
    +  show: function() {
    + + + + +
  • +
    + +
    + +
    +

    because we cannot redraw when hidden we may need to when becoming visible

    - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - var htmls = Mustache.render(this.template, tmplData); - this.$el.html(htmls); - this.$graph = this.$el.find('.panel.graph'); - this.$graph.on("plothover", this._toolTip); - return this; - }, +
    + +
        if (this.needToRedraw) {
    +      this.redraw();
    +    }
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    infoboxes on mouse hover on points/bars etc

    - remove: function () { - this.editor.remove(); - Backbone.View.prototype.remove.apply(this, arguments); - }, +
    + +
      _toolTip: function (event, pos, item) {
    +    if (item) {
    +      if (this.previousTooltipPoint.x !== item.dataIndex ||
    +          this.previousTooltipPoint.y !== item.seriesIndex) {
    +        this.previousTooltipPoint.x = item.dataIndex;
    +        this.previousTooltipPoint.y = item.seriesIndex;
    +        $("#recline-flot-tooltip").remove();
     
    -  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);
    -    if ((!areWeVisible || this.model.records.length === 0)) {
    -      this.needToRedraw = true;
    -      return;
    -    }

    check we have something to plot

        if (this.state.get('group') && this.state.get('series')) {
    -      var series = this.createSeries();
    -      var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length);
    -      this.plot = $.plot(this.$graph, series, options);
    -    }
    -  },
    +        var x = item.datapoint[0].toFixed(2),
    +            y = item.datapoint[1].toFixed(2);
     
    -  show: function() {

    because we cannot redraw when hidden we may need to when becoming visible

        if (this.needToRedraw) {
    -      this.redraw();
    -    }
    -  },

    infoboxes on mouse hover on points/bars etc

      _toolTip: function (event, pos, item) {
    -    if (item) {
    -      if (this.previousTooltipPoint.x !== item.dataIndex ||
    -          this.previousTooltipPoint.y !== item.seriesIndex) {
    -        this.previousTooltipPoint.x = item.dataIndex;
    -        this.previousTooltipPoint.y = item.seriesIndex;
    -        $("#recline-flot-tooltip").remove();
    +        if (this.state.attributes.graphType === 'bars') {
    +          x = item.datapoint[1].toFixed(2),
    +          y = item.datapoint[0].toFixed(2);
    +        }
     
    -        var x = item.datapoint[0].toFixed(2),
    -            y = item.datapoint[1].toFixed(2);
    +        var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
    +          group: this.state.attributes.group,
    +          x: this._xaxisLabel(x),
    +          series: item.series.label,
    +          y: y
    +        });
    + + + + +
  • +
    + +
    + +
    +

    use a different tooltip location offset for bar charts

    - if (this.state.attributes.graphType === 'bars') { - x = item.datapoint[1].toFixed(2), - y = item.datapoint[0].toFixed(2); - } +
    + +
            var xLocation, yLocation;
    +        if (this.state.attributes.graphType === 'bars') {
    +          xLocation = item.pageX + 15;
    +          yLocation = item.pageY - 10;
    +        } else if (this.state.attributes.graphType === 'columns') {
    +          xLocation = item.pageX + 15;
    +          yLocation = item.pageY;
    +        } else {
    +          xLocation = item.pageX + 10;
    +          yLocation = item.pageY - 20;
    +        }
     
    -        var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
    -          group: this.state.attributes.group,
    -          x: this._xaxisLabel(x),
    -          series: item.series.label,
    -          y: y
    -        });
  • use a different tooltip location offset for bar charts

            var xLocation, yLocation;
    -        if (this.state.attributes.graphType === 'bars') {
    -          xLocation = item.pageX + 15;
    -          yLocation = item.pageY - 10;
    -        } else if (this.state.attributes.graphType === 'columns') {
    -          xLocation = item.pageX + 15;
    -          yLocation = item.pageY;
    -        } else {
    -          xLocation = item.pageX + 10;
    -          yLocation = item.pageY - 20;
    -        }
    +        $('<div id="recline-flot-tooltip">' + content + '</div>').css({
    +            top: yLocation,
    +            left: xLocation
    +        }).appendTo("body").fadeIn(200);
    +      }
    +    } else {
    +      $("#recline-flot-tooltip").remove();
    +      this.previousTooltipPoint.x = null;
    +      this.previousTooltipPoint.y = null;
    +    }
    +  },
     
    -        $('<div id="recline-flot-tooltip">' + content + '</div>').css({
    -            top: yLocation,
    -            left: xLocation
    -        }).appendTo("body").fadeIn(200);
    -      }
    -    } else {
    -      $("#recline-flot-tooltip").remove();
    -      this.previousTooltipPoint.x = null;
    -      this.previousTooltipPoint.y = null;
    -    }
    -  },
    +  _xaxisLabel: function (x) {
    +    if (this._groupFieldIsDateTime()) {
    + + + + +
  • +
    + +
    + +
    +

    oddly x comes through as milliseconds string (rather than int +or float) so we have to reparse

    - _xaxisLabel: function (x) { - 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 + + +

          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);
    -    }
    +records not its actual x-axis value

    - return x; - },

    getGraphOptions

    + + +
          x = this.model.records.models[x].get(this.state.attributes.group);
    +    }
     
    +    return x;
    +  },
    + + + + +
  • +
    + +
    + +
    +

    getGraphOptions

    Get options for Flot Graph

    -

    needs to be function as can depend on state

    -

    @param typeId graphType id (lines, lines-and-points etc) -@param numPoints the number of points that will be plotted

  •   getGraphOptions: function(typeId, numPoints) {
    -    var self = this;
    -    var groupFieldIsDateTime = self._groupFieldIsDateTime();
    -    var xaxis = {};
    +@param numPoints the number of points that will be plotted

    - if (!groupFieldIsDateTime) { - xaxis.tickFormatter = function (x) {

    convert x to a string and make sure that it is not too long or the + + +

      getGraphOptions: function(typeId, numPoints) {
    +    var self = this;
    +    var groupFieldIsDateTime = self._groupFieldIsDateTime();
    +    var xaxis = {};
    +
    +    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

    - if (typeof label !== 'string') { - label = label.toString(); - } - if (self.state.attributes.graphType !== 'bars' && label.length > 10) { - label = label.slice(0, 10) + "..."; - } +
    + +
            var label = self._xaxisLabel(x) || "";
     
    -        return label;
    -      };
    -    }

    for labels case we only want ticks at the label intervals + if (typeof label !== 'string') { + label = label.toString(); + } + if (self.state.attributes.graphType !== 'bars' && label.length > 10) { + label = label.slice(0, 10) + "..."; + } + + 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 (var i=0; i<numTicks; i++) {
    -        ticks.push(parseInt(i*increment, 10));
    -      }
    -      xaxis.ticks = ticks;
    -    } else if (groupFieldIsDateTime) {
    -      xaxis.mode = 'time';
    -    }
    +could have a lot of values and so we limit to max 15 (we assume)

    - var yaxis = {}; - yaxis.autoscale = true; - yaxis.autoscaleMargin = 0.02; +
    + +
        if (this.xvaluesAreIndex) {
    +      var numTicks = Math.min(this.model.records.length, 15);
    +      var increment = this.model.records.length / numTicks;
    +      var ticks = [];
    +      for (var i=0; i<numTicks; i++) {
    +        ticks.push(parseInt(i*increment, 10));
    +      }
    +      xaxis.ticks = ticks;
    +    } else if (groupFieldIsDateTime) {
    +      xaxis.mode = 'time';
    +    }
     
    -    var legend = {};
    -    legend.position = 'ne';
    +    var yaxis = {};
    +    yaxis.autoscale = true;
    +    yaxis.autoscaleMargin = 0.02;
     
    -    var grid = {};
    -    grid.hoverable = true;
    -    grid.clickable = true;
    -    grid.borderColor = "#aaaaaa";
    -    grid.borderWidth = 1;
    +    var legend = {};
    +    legend.position = 'ne';
     
    -    var optionsPerGraphType = {
    -      lines: {
    -        legend: legend,
    -        colors: this.graphColors,
    -        lines: { show: true },
    -        xaxis: xaxis,
    -        yaxis: yaxis,
    -        grid: grid
    -      },
    -      points: {
    -        legend: legend,
    -        colors: this.graphColors,
    -        points: { show: true, hitRadius: 5 },
    -        xaxis: xaxis,
    -        yaxis: yaxis,
    -        grid: grid
    -      },
    -      'lines-and-points': {
    -        legend: legend,
    -        colors: this.graphColors,
    -        points: { show: true, hitRadius: 5 },
    -        lines: { show: true },
    -        xaxis: xaxis,
    -        yaxis: yaxis,
    -        grid: grid
    -      },
    -      bars: {
    -        legend: legend,
    -        colors: this.graphColors,
    -        lines: { show: false },
    -        xaxis: yaxis,
    -        yaxis: xaxis,
    -        grid: grid,
    -        bars: {
    -          show: true,
    -          horizontal: true,
    -          shadowSize: 0,
    -          align: 'center',
    -          barWidth: 0.8
    -        }
    -      },
    -      columns: {
    -        legend: legend,
    -        colors: this.graphColors,
    -        lines: { show: false },
    -        xaxis: xaxis,
    -        yaxis: yaxis,
    -        grid: grid,
    -        bars: {
    -          show: true,
    -          horizontal: false,
    -          shadowSize: 0,
    -          align: 'center',
    -          barWidth: 0.8
    -        }
    -      }
    -    };
    +    var grid = {};
    +    grid.hoverable = true;
    +    grid.clickable = true;
    +    grid.borderColor = "#aaaaaa";
    +    grid.borderWidth = 1;
     
    -    if (self.state.get('graphOptions')) {
    -      return _.extend(optionsPerGraphType[typeId],
    -                      self.state.get('graphOptions'));
    -    } else {
    -      return optionsPerGraphType[typeId];
    -    }
    -  },
    +    var optionsPerGraphType = {
    +      lines: {
    +        legend: legend,
    +        colors: this.graphColors,
    +        lines: { show: true },
    +        xaxis: xaxis,
    +        yaxis: yaxis,
    +        grid: grid
    +      },
    +      points: {
    +        legend: legend,
    +        colors: this.graphColors,
    +        points: { show: true, hitRadius: 5 },
    +        xaxis: xaxis,
    +        yaxis: yaxis,
    +        grid: grid
    +      },
    +      'lines-and-points': {
    +        legend: legend,
    +        colors: this.graphColors,
    +        points: { show: true, hitRadius: 5 },
    +        lines: { show: true },
    +        xaxis: xaxis,
    +        yaxis: yaxis,
    +        grid: grid
    +      },
    +      bars: {
    +        legend: legend,
    +        colors: this.graphColors,
    +        lines: { show: false },
    +        xaxis: yaxis,
    +        yaxis: xaxis,
    +        grid: grid,
    +        bars: {
    +          show: true,
    +          horizontal: true,
    +          shadowSize: 0,
    +          align: 'center',
    +          barWidth: 0.8
    +        }
    +      },
    +      columns: {
    +        legend: legend,
    +        colors: this.graphColors,
    +        lines: { show: false },
    +        xaxis: xaxis,
    +        yaxis: yaxis,
    +        grid: grid,
    +        bars: {
    +          show: true,
    +          horizontal: false,
    +          shadowSize: 0,
    +          align: 'center',
    +          barWidth: 0.8
    +        }
    +      }
    +    };
     
    -  _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;
    -  },
    +    if (self.state.get('graphOptions')) {
    +      return _.extend(optionsPerGraphType[typeId],
    +                      self.state.get('graphOptions'));
    +    } else {
    +      return optionsPerGraphType[typeId];
    +    }
    +  },
     
    -  createSeries: function() {
    -    var self = this;
    -    self.xvaluesAreIndex = false;
    -    var series = [];
    -    var xfield = self.model.fields.get(self.state.attributes.group);
    -    var isDateTime = self._groupFieldIsDateTime();
    +  _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;
    +  },
     
    -    _.each(this.state.attributes.series, function(field) {
    -      var points = [];
    -      var fieldLabel = self.model.fields.get(field).get('label');
    +  createSeries: function() {
    +    var self = this;
    +    self.xvaluesAreIndex = false;
    +    var series = [];
    +    var xfield = self.model.fields.get(self.state.attributes.group);
    +    var isDateTime = self._groupFieldIsDateTime();
     
    -        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)
    -                            });
    +    _.each(this.state.attributes.series, function(field) {
    +      var points = [];
    +      var fieldLabel = self.model.fields.get(field).get('label');
     
    -            if (_.all(raw, function(x){ return !isNaN(parseFloat(x)) })){
    -                var cast = function(x){ return parseFloat(x) }
    -            } else {
    -                self.xvaluesAreIndex = true
    -            }
    -        }
    +        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)
    +                            });
     
    -      _.each(self.model.records.models, function(doc, index) {
    -        if(self.xvaluesAreIndex){
    -            var x = index;
    -        }else{
    -            var x = cast(doc.getFieldValueUnrendered(xfield));
    -        }
    +            if (_.all(raw, function(x){ return !isNaN(parseFloat(x)) })){
    +                var cast = function(x){ return parseFloat(x) }
    +            } else {
    +                self.xvaluesAreIndex = true
    +            }
    +        }
     
    -        var yfield = self.model.fields.get(field);
    -        var y = doc.getFieldValueUnrendered(yfield);
    +      _.each(self.model.records.models, function(doc, index) {
    +        if(self.xvaluesAreIndex){
    +            var x = index;
    +        }else{
    +            var x = cast(doc.getFieldValueUnrendered(xfield));
    +        }
     
    -        if (self.state.attributes.graphType == 'bars') {
    -          points.push([y, x]);
    -        } else {
    -          points.push([x, y]);
    -        }
    -      });
    -      series.push({
    -        data: points,
    -        label: fieldLabel,
    -        hoverable: true
    -      });
    -    });
    -    return series;
    -  }
    -});
    +        var yfield = self.model.fields.get(field);
    +        var y = doc.getFieldValueUnrendered(yfield);
     
    -my.FlotControls = Backbone.View.extend({
    -  className: "editor",
    -  template: ' \
    -  <div class="editor"> \
    -    <form class="form-stacked"> \
    -      <div class="clearfix"> \
    -        <label>Graph Type</label> \
    -        <div class="input editor-type"> \
    -          <select> \
    -          <option value="lines-and-points">Lines and Points</option> \
    -          <option value="lines">Lines</option> \
    -          <option value="points">Points</option> \
    -          <option value="bars">Bars</option> \
    -          <option value="columns">Columns</option> \
    -          </select> \
    -        </div> \
    -        <label>Group Column (Axis 1)</label> \
    -        <div class="input editor-group"> \
    -          <select> \
    -          <option value="">Please choose ...</option> \
    -          {{#fields}} \
    -          <option value="{{id}}">{{label}}</option> \
    -          {{/fields}} \
    -          </select> \
    -        </div> \
    -        <div class="editor-series-group"> \
    -        </div> \
    -      </div> \
    -      <div class="editor-buttons"> \
    -        <button class="btn editor-add">Add Series</button> \
    -      </div> \
    -      <div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
    -        <button class="editor-save">Save</button> \
    -        <input type="hidden" class="editor-id" value="chart-1" /> \
    -      </div> \
    -    </form> \
    -  </div> \
    -',
    -  templateSeriesEditor: ' \
    -    <div class="editor-series js-series-{{seriesIndex}}"> \
    -      <label>Series <span>{{seriesName}} (Axis 2)</span> \
    -        [<a href="#remove" class="action-remove-series">Remove</a>] \
    -      </label> \
    -      <div class="input"> \
    -        <select> \
    -        {{#fields}} \
    -        <option value="{{id}}">{{label}}</option> \
    -        {{/fields}} \
    -        </select> \
    -      </div> \
    -    </div> \
    -  ',
    -  events: {
    -    'change form select': 'onEditorSubmit',
    -    'click .editor-add': '_onAddSeries',
    -    'click .action-remove-series': 'removeSeries'
    -  },
    +        if (self.state.attributes.graphType == 'bars') {
    +          points.push([y, x]);
    +        } else {
    +          points.push([x, y]);
    +        }
    +      });
    +      series.push({
    +        data: points,
    +        label: fieldLabel,
    +        hoverable: true
    +      });
    +    });
    +    return series;
    +  }
    +});
     
    -  initialize: function(options) {
    -    var self = this;
    -    _.bindAll(this, 'render');
    -    this.listenTo(this.model.fields, 'reset add', this.render);
    -    this.state = new recline.Model.ObjectState(options.state);
    -    this.render();
    -  },
    +my.FlotControls = Backbone.View.extend({
    +  className: "editor",
    +  template: ' \
    +  <div class="editor"> \
    +    <form class="form-stacked"> \
    +      <div class="clearfix"> \
    +        <div class="form-group"> \
    +          <label>Graph Type</label> \
    +          <div class="input editor-type"> \
    +            <select class="form-control"> \
    +              <option value="lines-and-points">Lines and Points</option> \
    +              <option value="lines">Lines</option> \
    +              <option value="points">Points</option> \
    +              <option value="bars">Bars</option> \
    +              <option value="columns">Columns</option> \
    +            </select> \
    +          </div> \
    +        </div> \
    +        <div class="form-group"> \
    +          <label>Group Column (Axis 1)</label> \
    +          <div class="input editor-group"> \
    +            <select class="form-control"> \
    +              <option value="">Please choose ...</option> \
    +                {{#fields}} \
    +              <option value="{{id}}">{{label}}</option> \
    +                {{/fields}} \
    +            </select> \
    +          </div> \
    +        </div> \
    +        <div class="editor-series-group"> \
    +        </div> \
    +      </div> \
    +      <div class="editor-buttons"> \
    +        <button class="btn btn-default editor-add">Add Series</button> \
    +      </div> \
    +      <div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
    +        <button class="editor-save">Save</button> \
    +        <input type="hidden" class="editor-id" value="chart-1" /> \
    +      </div> \
    +    </form> \
    +  </div> \
    +',
    +  templateSeriesEditor: ' \
    +    <div class="editor-series js-series-{{seriesIndex}}"> \
    +      <div class="form-group"> \
    +        <label>Series <span>{{seriesName}} (Axis 2)</span> \
    +          [<a href="#remove" class="action-remove-series">Remove</a>] \
    +        </label> \
    +        <div class="input"> \
    +          <select class="form-control"> \
    +          {{#fields}} \
    +          <option value="{{id}}">{{label}}</option> \
    +          {{/fields}} \
    +          </select> \
    +        </div> \
    +      </div> \
    +    </div> \
    +  ',
    +  events: {
    +    'change form select': 'onEditorSubmit',
    +    'click .editor-add': '_onAddSeries',
    +    'click .action-remove-series': 'removeSeries'
    +  },
     
    -  render: function() {
    -    var self = this;
    -    var tmplData = this.model.toTemplateJSON();
    -    var htmls = Mustache.render(this.template, tmplData);
    -    this.$el.html(htmls);

    set up editor from state

        if (this.state.get('graphType')) {
    -      this._selectOption('.editor-type', this.state.get('graphType'));
    -    }
    -    if (this.state.get('group')) {
    -      this._selectOption('.editor-group', this.state.get('group'));
    -    }

    ensure at least one series box shows up

        var tmpSeries = [""];
    -    if (this.state.get('series').length > 0) {
    -      tmpSeries = this.state.get('series');
    -    }
    -    _.each(tmpSeries, function(series, idx) {
    -      self.addSeries(idx);
    -      self._selectOption('.editor-series.js-series-' + idx, series);
    -    });
    -    return this;
    -  },

    Private: Helper function to select an option from a select list

      _selectOption: function(id,value){
    -    var options = this.$el.find(id + ' select > option');
    -    if (options) {
    -      options.each(function(opt){
    -        if (this.value == value) {
    -          $(this).attr('selected','selected');
    -          return false;
    -        }
    -      });
    -    }
    -  },
    +  initialize: function(options) {
    +    var self = this;
    +    _.bindAll(this, 'render');
    +    this.listenTo(this.model.fields, 'reset add', this.render);
    +    this.state = new recline.Model.ObjectState(options.state);
    +    this.render();
    +  },
     
    -  onEditorSubmit: function(e) {
    -    var select = this.$el.find('.editor-group select');
    -    var $editor = this;
    -    var $series = this.$el.find('.editor-series select');
    -    var series = $series.map(function () {
    -      return $(this).val();
    -    });
    -    var updatedState = {
    -      series: $.makeArray(series),
    -      group: this.$el.find('.editor-group select').val(),
    -      graphType: this.$el.find('.editor-type select').val()
    -    };
    -    this.state.set(updatedState);
    -  },

    Public: Adds a new empty series select box to the editor.

    + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + this.$el.html(htmls); + + + + +
  • +
    + +
    + +
    +

    set up editor from state

    +
    + +
        if (this.state.get('graphType')) {
    +      this._selectOption('.editor-type', this.state.get('graphType'));
    +    }
    +    if (this.state.get('group')) {
    +      this._selectOption('.editor-group', this.state.get('group'));
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    ensure at least one series box shows up

    + +
    + +
        var tmpSeries = [""];
    +    if (this.state.get('series').length > 0) {
    +      tmpSeries = this.state.get('series');
    +    }
    +    _.each(tmpSeries, function(series, idx) {
    +      self.addSeries(idx);
    +      self._selectOption('.editor-series.js-series-' + idx, series);
    +    });
    +    return this;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Helper function to select an option from a select list

    + +
    + +
      _selectOption: function(id,value){
    +    var options = this.$el.find(id + ' select > option');
    +    if (options) {
    +      options.each(function(opt){
    +        if (this.value == value) {
    +          $(this).attr('selected','selected');
    +          return false;
    +        }
    +      });
    +    }
    +  },
    +
    +  onEditorSubmit: function(e) {
    +    var select = this.$el.find('.editor-group select');
    +    var $editor = this;
    +    var $series = this.$el.find('.editor-series select');
    +    var series = $series.map(function () {
    +      return $(this).val();
    +    });
    +    var updatedState = {
    +      series: $.makeArray(series),
    +      group: this.$el.find('.editor-group select').val(),
    +      graphType: this.$el.find('.editor-type select').val()
    +    };
    +    this.state.set(updatedState);
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Public: Adds a new empty series select box to the editor.

    @param [int] idx index of this series in the list of series

    +

    Returns itself.

    -

    Returns itself.

  •   addSeries: function (idx) {
    -    var data = _.extend({
    -      seriesIndex: idx,
    -      seriesName: String.fromCharCode(idx + 64 + 1)
    -    }, this.model.toTemplateJSON());
    +            
    + +
      addSeries: function (idx) {
    +    var data = _.extend({
    +      seriesIndex: idx,
    +      seriesName: String.fromCharCode(idx + 64 + 1)
    +    }, this.model.toTemplateJSON());
     
    -    var htmls = Mustache.render(this.templateSeriesEditor, data);
    -    this.$el.find('.editor-series-group').append(htmls);
    -    return this;
    -  },
    +    var htmls = Mustache.render(this.templateSeriesEditor, data);
    +    this.$el.find('.editor-series-group').append(htmls);
    +    return this;
    +  },
     
    -  _onAddSeries: function(e) {
    -    e.preventDefault();
    -    this.addSeries(this.state.get('series').length);
    -  },

    Public: Removes a series list item from the editor.

    + _onAddSeries: function(e) { + e.preventDefault(); + this.addSeries(this.state.get('series').length); + }, + + + + +
  • +
    + +
    + +
    +

    Public: Removes a series list item from the editor.

    +

    Also updates the labels of the remaining series elements.

    -

    Also updates the labels of the remaining series elements.

  •   removeSeries: function (e) {
    -    e.preventDefault();
    -    var $el = $(e.target);
    -    $el.parent().parent().remove();
    -    this.onEditorSubmit();
    -  }
    -});
    +            
    + +
      removeSeries: function (e) {
    +    e.preventDefault();
    +    var $el = $(e.target);
    +    $el.parent().parent().remove();
    +    this.onEditorSubmit();
    +  }
    +});
     
    -})(jQuery, recline.View);
    -
    -
    \ No newline at end of file +})(jQuery, recline.View);
  • + + + + +
    + + diff --git a/docs/src/view.graph.html b/docs/src/view.graph.html index bf5b23a5..a6cbb03d 100644 --- a/docs/src/view.graph.html +++ b/docs/src/view.graph.html @@ -1,6 +1,141 @@ - view.graph.js \ No newline at end of file + + + 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 7f1fc32d..09a91eca 100644 --- a/docs/src/view.grid.html +++ b/docs/src/view.grid.html @@ -1,232 +1,606 @@ - view.grid.js

    view.grid.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  view.grid.js
    +  
    +  
    +  
    +
    +
    +  

    (Data) Grid Dataset View

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +(function($, my) { + "use strict"; + + + + +
  • +
    + +
    + +
    +

    (Data) Grid Dataset View

    Provides a tabular view on a Dataset.

    +

    Initialize it with a recline.Model.Dataset.

    -

    Initialize it with a recline.Model.Dataset.

  • my.Grid = Backbone.View.extend({
    -  tagName:  "div",
    -  className: "recline-grid-container",
    +            
    + +
    my.Grid = Backbone.View.extend({
    +  tagName:  "div",
    +  className: "recline-grid-container",
     
    -  initialize: function(modelEtc) {
    -    var self = this;
    -    _.bindAll(this, 'render', 'onHorizontalScroll');
    -    this.listenTo(this.model.records, 'add reset remove', this.render);
    -    this.tempState = {};
    -    var state = _.extend({
    -        hiddenFields: []
    -      }, modelEtc.state
    -    ); 
    -    this.state = new recline.Model.ObjectState(state);
    -  },
    +  initialize: function(modelEtc) {
    +    var self = this;
    +    _.bindAll(this, 'render', 'onHorizontalScroll');
    +    this.listenTo(this.model.records, 'add reset remove', this.render);
    +    this.tempState = {};
    +    var state = _.extend({
    +        hiddenFields: []
    +      }, modelEtc.state
    +    ); 
    +    this.state = new recline.Model.ObjectState(state);
    +  },
     
    -  events: {

    does not work here so done at end of render function -'scroll .recline-grid tbody': 'onHorizontalScroll'

      },

    ====================================================== -Column and row menus

      setColumnSort: function(order) {
    -    var sort = [{}];
    -    sort[0][this.tempState.currentColumn] = {order: order};
    -    this.model.query({sort: sort});
    -  },
    +  events: {
    + + + + +
  • +
    + +
    + +
    +

    does not work here so done at end of render function +‘scroll .recline-grid tbody’: ‘onHorizontalScroll’

    + +
    + +
      },
    + +
  • + + +
  • +
    + +
    + +
    +

    ======================================================

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Column and row menus

    + +
    + +
    +  setColumnSort: function(order) {
    +    var sort = [{}];
    +    sort[0][this.tempState.currentColumn] = {order: order};
    +    this.model.query({sort: sort});
    +  },
       
    -  hideColumn: function() {
    -    var hiddenFields = this.state.get('hiddenFields');
    -    hiddenFields.push(this.tempState.currentColumn);
    -    this.state.set({hiddenFields: hiddenFields});
  • change event not being triggered (because it is an array?) so trigger manually

        this.state.trigger('change');
    -    this.render();
    -  },
    +  hideColumn: function() {
    +    var hiddenFields = this.state.get('hiddenFields');
    +    hiddenFields.push(this.tempState.currentColumn);
    +    this.state.set({hiddenFields: hiddenFields});
    + + + + +
  • +
    + +
    + +
    +

    change event not being triggered (because it is an array?) so trigger manually

    + +
    + +
        this.state.trigger('change');
    +    this.render();
    +  },
       
    -  showColumn: function(e) {
    -    var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column'));
    -    this.state.set({hiddenFields: hiddenFields});
    -    this.render();
    -  },
    +  showColumn: function(e) {
    +    var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column'));
    +    this.state.set({hiddenFields: hiddenFields});
    +    this.render();
    +  },
     
    -  onHorizontalScroll: function(e) {
    -    var currentScroll = $(e.target).scrollLeft();
    -    this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll);
    -  },
  • ======================================================

    + onHorizontalScroll: function(e) { + var currentScroll = $(e.target).scrollLeft(); + this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); + }, + + + + +
  • +
    + +
    + +
    +

    ======================================================

    -

    Templating

  •   template: ' \
    -    <div class="table-container"> \
    -    <table class="recline-grid table-striped table-condensed" cellspacing="0"> \
    -      <thead class="fixed-header"> \
    -        <tr> \
    -          {{#fields}} \
    -            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;" title="{{label}}"> \
    -              <span class="column-header-name">{{label}}</span> \
    -            </th> \
    -          {{/fields}} \
    -          <th class="last-header" style="width: {{lastHeaderWidth}}px; max-width: {{lastHeaderWidth}}px; min-width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
    -        </tr> \
    -      </thead> \
    -      <tbody class="scroll-content"></tbody> \
    -    </table> \
    -    </div> \
    -  ',
    +            
    + + + + +
  • +
    + +
    + +
    +

    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 = 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 = new recline.Model.FieldList(this.model.fields.filter(function(field) {
    -      return _.indexOf(self.state.get('hiddenFields'), field.id) == -1;
    -    }));
    +            
    + +
      template: ' \
    +    <div class="table-container"> \
    +    <table class="recline-grid table-striped table-condensed" cellspacing="0"> \
    +      <thead class="fixed-header"> \
    +        <tr> \
    +          {{#fields}} \
    +            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;" title="{{label}}"> \
    +              <span class="column-header-name">{{label}}</span> \
    +            </th> \
    +          {{/fields}} \
    +          <th class="last-header" style="width: {{lastHeaderWidth}}px; max-width: {{lastHeaderWidth}}px; min-width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
    +        </tr> \
    +      </thead> \
    +      <tbody class="scroll-content"></tbody> \
    +    </table> \
    +    </div> \
    +  ',
     
    -    this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions
    -    var numFields = this.fields.length;

    compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)

        var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width;
    -    var width = parseInt(Math.max(50, fullWidth / numFields), 10);

    if columns extend outside viewport then remainder is 0

        var remainder = Math.max(fullWidth - numFields * width,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.model.records.forEach(function(doc) {
    -      var tr = $('<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];
    -    if ($tbody.scrollHeight <= $tbody.offsetHeight) {
    -      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);
    -    return this;
    -  },

    _scrollbarSize

    + 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 = 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 = 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 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);
    +    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.model.records.forEach(function(doc) {
    +      var tr = $('<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];
    +    if ($tbody.scrollHeight <= $tbody.offsetHeight) {
    +      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);
    +    return this;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    _scrollbarSize

    Measure width of a vertical scrollbar and height of a horizontal scrollbar.

    +

    @return: { width: pixelWidth, height: pixelHeight }

    -

    @return: { width: pixelWidth, height: pixelHeight }

  •   _scrollbarSize: function() {
    -    var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
    -    var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
    -    $c.remove();
    -    return dim;
    -  }
    -});

    GridRow View for rendering an individual record.

    - + + +
      _scrollbarSize: function() {
    +    var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
    +    var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
    +    $c.remove();
    +    return dim;
    +  }
    +});
    + + + + +
  • +
    + +
    + +
    +

    GridRow View for rendering an individual record.

    Since we want this to update in place it is up to creator to provider the element to attach to.

    -

    In addition you must pass in a FieldList in the constructor options. This should be list of fields for the Grid.

    -

    Example:

    -
     var row = new GridRow({
       model: dataset-record,
         el: dom-element,
         fields: mydatasets.fields // a FieldList object
       });
    -
  • my.GridRow = Backbone.View.extend({
    -  initialize: function(initData) {
    -    _.bindAll(this, 'render');
    -    this._fields = initData.fields;
    -    this.listenTo(this.model, 'change', this.render);
    -  },
    +
    - template: ' \ - {{#cells}} \ - <td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;"> \ - <div class="data-table-cell-content"> \ - <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \ - <div class="data-table-cell-value">{{{value}}}</div> \ - </div> \ - </td> \ - {{/cells}} \ - ', - events: { - 'click .data-table-cell-edit': 'onEditClick', - 'click .data-table-cell-editor .okButton': 'onEditorOK', - 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' - }, +
    + +
    my.GridRow = Backbone.View.extend({
    +  initialize: function(initData) {
    +    _.bindAll(this, 'render');
    +    this._fields = initData.fields;
    +    this.listenTo(this.model, 'change', this.render);
    +  },
    +
    +  template: ' \
    +      {{#cells}} \
    +      <td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;"> \
    +        <div class="data-table-cell-content"> \
    +          <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
    +          <div class="data-table-cell-value">{{{value}}}</div> \
    +        </div> \
    +      </td> \
    +      {{/cells}} \
    +    ',
    +  events: {
    +    'click .data-table-cell-edit': 'onEditClick',
    +    'click .data-table-cell-editor .okButton': 'onEditorOK',
    +    'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
    +  },
       
    -  toTemplateJSON: function() {
    -    var self = this;
    -    var doc = this.model;
    -    var cellData = this._fields.map(function(field) {
    -      return {
    -        field: field.id,
    -        width: field.get('width'),
    -        value: doc.getFieldValue(field)
    -      };
    -    });
    -    return { id: this.id, cells: cellData };
    -  },
    +  toTemplateJSON: function() {
    +    var self = this;
    +    var doc = this.model;
    +    var cellData = this._fields.map(function(field) {
    +      return {
    +        field: field.id,
    +        width: field.get('width'),
    +        value: doc.getFieldValue(field)
    +      };
    +    });
    +    return { id: this.id, cells: cellData };
    +  },
     
    -  render: function() {
    -    this.$el.attr('data-id', this.model.id);
    -    var html = Mustache.render(this.template, this.toTemplateJSON());
    -    this.$el.html(html);
    -    return this;
    -  },

    =================== -Cell Editor methods

      cellEditorTemplate: ' \
    -    <div class="menu-container data-table-cell-editor"> \
    -      <textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
    -      <div id="data-table-cell-editor-actions"> \
    -        <div class="data-table-cell-editor-action"> \
    -          <button class="okButton btn primary">Update</button> \
    -          <button class="cancelButton btn danger">Cancel</button> \
    -        </div> \
    -      </div> \
    -    </div> \
    -  ',
    +  render: function() {
    +    this.$el.attr('data-id', this.model.id);
    +    var html = Mustache.render(this.template, this.toTemplateJSON());
    +    this.$el.html(html);
    +    return this;
    +  },
    + + + + +
  • +
    + +
    + +
    +

    ===================

    - onEditClick: function(e) { - 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"); - } - $(e.target).addClass("hidden"); - var cell = $(e.target).siblings('.data-table-cell-value'); - cell.data("previousContents", cell.text()); - var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()}); - cell.html(templated); - }, +
    + +
  • + + +
  • +
    + +
    + +
    +

    Cell Editor methods

    - onEditorOK: function(e) { - var self = this; - var cell = $(e.target); - var rowId = cell.parents('tr').attr('data-id'); - var field = cell.parents('td').attr('data-field'); - var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); - var newData = {}; - newData[field] = newValue; - this.model.set(newData); - this.trigger('recline:flash', {message: "Updating row...", loader: true}); - this.model.save().then(function(response) { - this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'}); - }) - .fail(function() { - this.trigger('recline:flash', { - message: 'Error saving row', - category: 'error', - persist: true - }); - }); - }, +
    + +
    +  cellEditorTemplate: ' \
    +    <div class="menu-container data-table-cell-editor"> \
    +      <textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
    +      <div id="data-table-cell-editor-actions"> \
    +        <div class="data-table-cell-editor-action"> \
    +          <button class="okButton btn primary">Update</button> \
    +          <button class="cancelButton btn danger">Cancel</button> \
    +        </div> \
    +      </div> \
    +    </div> \
    +  ',
     
    -  onEditorCancel: function(e) {
    -    var cell = $(e.target).parents('.data-table-cell-value');
    -    cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
    -  }
    -});
    +  onEditClick: function(e) {
    +    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");
    +    }
    +    $(e.target).addClass("hidden");
    +    var cell = $(e.target).siblings('.data-table-cell-value');
    +    cell.data("previousContents", cell.text());
    +    var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()});
    +    cell.html(templated);
    +  },
     
    -})(jQuery, recline.View);
    +  onEditorOK: function(e) {
    +    var self = this;
    +    var cell = $(e.target);
    +    var rowId = cell.parents('tr').attr('data-id');
    +    var field = cell.parents('td').attr('data-field');
    +    var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
    +    var newData = {};
    +    newData[field] = newValue;
    +    this.model.set(newData);
    +    this.trigger('recline:flash', {message: "Updating row...", loader: true});
    +    this.model.save().then(function(response) {
    +        this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'});
    +      })
    +      .fail(function() {
    +        this.trigger('recline:flash', {
    +          message: 'Error saving row',
    +          category: 'error',
    +          persist: true
    +        });
    +      });
    +  },
     
    -
  • \ No newline at end of file + onEditorCancel: function(e) { + var cell = $(e.target).parents('.data-table-cell-value'); + cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); + } +}); + +})(jQuery, recline.View);
    + + + + +
    + + diff --git a/docs/src/view.map.html b/docs/src/view.map.html index d5115b00..85c72ff3 100644 --- a/docs/src/view.map.html +++ b/docs/src/view.map.html @@ -1,26 +1,162 @@ - view.map.js

    view.map.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  view.map.js
    +  
    +  
    +  
    +
    +
    +  

    Map view for a Dataset using Leaflet mapping library.

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +(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:

    -
    1. Via a single field. This field must be either a geo_point or GeoJSON object
    2. Via two fields with latitude and longitude coordinates.
    -

    Which fields in the data these correspond to can be configured via the state (and are guessed if no info is provided).

    -

    Initialization arguments are as standard for Dataset Views. State object may have the following (optional) configuration options:

    -
       {
         // geomField if specified will be used in preference to lat/lon
    @@ -36,522 +172,1160 @@ have the following (optional) configuration options:

    Useful attributes to know about (if e.g. customizing)

    -
    • map: the Leaflet map (L.Map)
    • features: Leaflet GeoJSON layer containing all the features (L.GeoJSON)
    • -
  • my.Map = Backbone.View.extend({
    -  template: ' \
    -    <div class="recline-map"> \
    -      <div class="panel map"></div> \
    -    </div> \
    -',

    These are the default (case-insensitive) names of field that are used if found. -If not found, the user will need to define the fields via the editor.

      latitudeFieldNames: ['lat','latitude'],
    -  longitudeFieldNames: ['lon','longitude'],
    -  geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'],
    +
     
    -  initialize: function(options) {
    -    var self = this;
    -    this.visible = true;
    -    this.mapReady = false;

    this will be the Leaflet L.Map object (setup below)

        this.map = null;
    +            
    + +
    my.Map = Backbone.View.extend({
    +  template: ' \
    +    <div class="recline-map"> \
    +      <div class="panel map"></div> \
    +    </div> \
    +',
    + + + + +
  • +
    + +
    + +
    +

    These are the default (case-insensitive) names of field that are used if found. +If not found, the user will need to define the fields via the editor.

    - var stateData = _.extend({ - geomField: null, - lonField: null, - latField: null, - autoZoom: true, - cluster: false - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); +
    + +
      latitudeFieldNames: ['lat','latitude'],
    +  longitudeFieldNames: ['lon','longitude'],
    +  geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'],
     
    -    this._clusterOptions = {
    -      zoomToBoundsOnClick: true,
  • disableClusteringAtZoom: 10,

          maxClusterRadius: 80,
    -      singleMarkerMode: false,
    -      skipDuplicateAddTesting: true,
    -      animateAddingMarkers: false
    -    };

    Listen to changes in the fields

        this.listenTo(this.model.fields, 'change', function() {
    -      self._setupGeometryField();
    -      self.render();
    -    });

    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.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);});
    -    this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');});
    +  initialize: function(options) {
    +    var self = this;
    +    this.visible = this.$el.is(':visible');
    +    this.mapReady = false;
    + + + + +
  • +
    + +
    + +
    +

    this will be the Leaflet L.Map object (setup below)

    - this.menu = new my.MapMenu({ - model: this.model, - state: this.state.toJSON() - }); - this.listenTo(this.menu.state, 'change', function() { - self.state.set(self.menu.state.toJSON()); - self.redraw(); - }); - this.listenTo(this.state, 'change', function() { - self.redraw(); - }); - this.elSidebar = this.menu.$el; - },
  • Customization Functions

    + + +
        this.map = null;
     
    +    var stateData = _.extend({
    +        geomField: null,
    +        lonField: null,
    +        latField: null,
    +        autoZoom: true,
    +        cluster: false
    +      },
    +      options.state
    +    );
    +    this.state = new recline.Model.ObjectState(stateData);
    +
    +    this._clusterOptions = {
    +      zoomToBoundsOnClick: true,
    + + + + +
  • +
    + +
    + +
    +

    disableClusteringAtZoom: 10,

    + +
    + +
          maxClusterRadius: 80,
    +      singleMarkerMode: false,
    +      skipDuplicateAddTesting: true,
    +      animateAddingMarkers: false
    +    };
    + +
  • + + +
  • +
    + +
    + +
    +

    Listen to changes in the fields

    + +
    + +
        this.listenTo(this.model.fields, 'change', function() {
    +      self._setupGeometryField();
    +      self.render();
    +    });
    + +
  • + + +
  • +
    + +
    + +
    +

    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.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.listenTo(this.menu.state, 'change', function() {
    +      self.state.set(self.menu.state.toJSON());
    +      self.redraw();
    +    });
    +    this.listenTo(this.state, 'change', function() {
    +      self.redraw();
    +    });
    +    this.elSidebar = this.menu.$el;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Customization Functions

    The following methods are designed for overriding in order to customize -behaviour

  • infobox

    +behaviour

    + + + + + +
  • +
    + +
    + +
    +

    infobox

    Function to create infoboxes used in popups. The default behaviour is very simple and just lists all attributes.

    -

    Users should override this function to customize behaviour i.e.

    - -
    view = new View({...});
    -view.infobox = function(record) {
    +
    view = new View({...});
    +view.infobox = function(record) {
       ...
     }
    -
  •   infobox: function(record) {
    -    var html = '';
    -    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>';
    -      }
    -    }
    -    return html;
    -  },

    Options to use for the Leaflet GeoJSON layer -See also http://leaflet.cloudmade.com/examples/geojson.html

    - -

    e.g.

    - -
    pointToLayer: function(feature, latLng)
    -onEachFeature: function(feature, layer)
     
    + + +
      infobox: function(record) {
    +    var html = '';
    +    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>';
    +      }
    +    }
    +    return html;
    +  },
    + + + + +
  • +
    + +
    + +
    +

    Options to use for the Leaflet GeoJSON layer +See also http://leaflet.cloudmade.com/examples/geojson.html

    +

    e.g.

    +
    pointToLayer: function(feature, latLng)
    +onEachFeature: function(feature, layer)
    +

    See defaults for examples

    -

    See defaults for examples

  •   geoJsonLayerOptions: {

    pointToLayer function to use when creating points

    - + + +
      geoJsonLayerOptions: {
    + + + + +
  • +
    + +
    + +
    +

    pointToLayer function to use when creating points

    Default behaviour shown here is to create a marker using the popupContent set on the feature properties (created via infobox function during feature generation)

    -

    NB: inside pointToLayer this will be set to point to this map view -instance (which allows e.g. this.markers to work in this default case)

  •     pointToLayer: function (feature, latlng) {
    -      var marker = new L.Marker(latlng);
    -      marker.bindPopup(feature.properties.popupContent);

    this is for cluster case

          this.markers.addLayer(marker);
    -      return marker;
    -    },

    onEachFeature default which adds popup in

        onEachFeature: function(feature, layer) {
    -      if (feature.properties && feature.properties.popupContent) {
    -        layer.bindPopup(feature.properties.popupContent);
    -      }
    -    }
    -  },

    END: Customization section

    Public: Adds the necessary elements to the page.

    +instance (which allows e.g. this.markers to work in this default case)

    -

    Also sets up the editor fields and the map if necessary.

      render: function() {
    -    var self = this;
    -    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

    + + +
        pointToLayer: function (feature, latlng) {
    +      var marker = new L.Marker(latlng);
    +      marker.bindPopup(feature.properties.popupContent);
    + + + + +
  • +
    + +
    + +
    +

    this is for cluster case

    +
    + +
          this.markers.addLayer(marker);
    +      return marker;
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    onEachFeature default which adds popup in

    + +
    + +
        onEachFeature: function(feature, layer) {
    +      if (feature.properties && feature.properties.popupContent) {
    +        layer.bindPopup(feature.properties.popupContent);
    +      }
    +    }
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    END: Customization section

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Public: Adds the necessary elements to the page.

    +

    Also sets up the editor fields and the map if necessary.

    + +
    + +
      render: function() {
    +    var self = this;
    +    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

    Actions can be:

    -
    • reset: Clear all features
    • add: Add one or n features (records)
    • remove: Remove one or n features (records)
    • refresh: Clear existing features and add all current records
    • -
  •   redraw: function(action, doc){
    -    var self = this;
    -    action = action || 'refresh';

    try to set things up if not already

        if (!self._geomReady()){
    -      self._setupGeometryField();
    -    }
    -    if (!self.mapReady){
    -      self._setupMap();
    -    }
    +
     
    -    if (this._geomReady() && this.mapReady){

    removing ad re-adding the layer enables faster bulk loading

          this.map.removeLayer(this.features);
    -      this.map.removeLayer(this.markers);
    +            
    + +
      redraw: function(action, doc){
    +    var self = this;
    +    action = action || 'refresh';
    + + + + +
  • +
    + +
    + +
    +

    try to set things up if not already

    - var countBefore = 0; - this.features.eachLayer(function(){countBefore++;}); +
    + +
        if (!self._geomReady()){
    +      self._setupGeometryField();
    +    }
    +    if (!self.mapReady){
    +      self._setupMap();
    +    }
     
    -      if (action == 'refresh' || action == 'reset') {
    -        this.features.clearLayers();
  • recreate cluster group because of issues with clearLayer

            this.map.removeLayer(this.markers);
    -        this.markers = new L.MarkerClusterGroup(this._clusterOptions);
    -        this._add(this.model.records.models);
    -      } else if (action == 'add' && doc){
    -        this._add(doc);
    -      } else if (action == 'remove' && doc){
    -        this._remove(doc);
    -      }

    this must come before zooming! + if (this._geomReady() && this.mapReady){ + + + + +

  • +
    + +
    + +
    +

    removing ad re-adding the layer enables faster bulk loading

    + +
    + +
          this.map.removeLayer(this.features);
    +      this.map.removeLayer(this.markers);
    +
    +      var countBefore = 0;
    +      this.features.eachLayer(function(){countBefore++;});
    +
    +      if (action == 'refresh' || action == 'reset') {
    +        this.features.clearLayers();
    + +
  • + + +
  • +
    + +
    + +
    +

    recreate cluster group because of issues with clearLayer

    + +
    + +
            this.map.removeLayer(this.markers);
    +        this.markers = new L.MarkerClusterGroup(this._clusterOptions);
    +        this._add(this.model.records.models);
    +      } else if (action == 'add' && doc){
    +        this._add(doc);
    +      } else if (action == 'remove' && doc){
    +        this._remove(doc);
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    this must come before zooming! if not: errors when using e.g. circle markers like -"Cannot call method 'project' of undefined"

  •       if (this.state.get('cluster')) {
    -        this.map.addLayer(this.markers);
    -      } else {
    -        this.map.addLayer(this.features);
    -      }
    +“Cannot call method ‘project’ of undefined”

    - if (this.state.get('autoZoom')){ - if (this.visible){ - this._zoomToFeatures(); - } else { - this._zoomPending = true; - } - } - } - }, +
    + +
          if (this.state.get('cluster')) {
    +        this.map.addLayer(this.markers);
    +      } else {
    +        this.map.addLayer(this.features);
    +      }
     
    -  show: function() {

    If the div was hidden, Leaflet needs to recalculate some sizes -to display properly

        if (this.map){
    -      this.map.invalidateSize();
    -      if (this._zoomPending && this.state.get('autoZoom')) {
    -        this._zoomToFeatures();
    -        this._zoomPending = false;
    -      }
    -    }
    -    this.visible = true;
    -  },
    +      if (this.state.get('autoZoom')){
    +        if (this.visible){
    +          this._zoomToFeatures();
    +        } else {
    +          this._zoomPending = true;
    +        }
    +      }
    +    }
    +  },
     
    -  hide: function() {
    -    this.visible = false;
    -  },
    +  show: function() {
    + + + + +
  • +
    + +
    + +
    +

    If the div was hidden, Leaflet needs to recalculate some sizes +to display properly

    - _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

    + + +
        if (this.map){
    +      this.map.invalidateSize();
    +      if (this._zoomPending && this.state.get('autoZoom')) {
    +        this._zoomToFeatures();
    +        this._zoomPending = false;
    +      }
    +    }
    +    this.visible = true;
    +  },
     
    +  hide: function() {
    +    this.visible = false;
    +  },
    +
    +  _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

    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 stopped and an error notification shown.

    +

    Each feature will have a popup associated with all the record fields.

    -

    Each feature will have a popup associated with all the record fields.

  •   _add: function(docs){
    -    var self = this;
    +            
    + +
      _add: function(docs){
    +    var self = this;
     
    -    if (!(docs instanceof Array)) docs = [docs];
    +    if (!(docs instanceof Array)) docs = [docs];
     
    -    var count = 0;
    -    var wrongSoFar = 0;
    -    _.every(docs, function(doc){
    -      count += 1;
    -      var feature = self._getGeometryFromRecord(doc);
    -      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 -link this Leaflet layer to a Recline doc

              cid: doc.cid
    -        };
    +    var count = 0;
    +    var wrongSoFar = 0;
    +    _.every(docs, function(doc){
    +      count += 1;
    +      var feature = self._getGeometryFromRecord(doc);
    +      if (typeof feature === 'undefined' || feature === null){
    + + + + +
  • +
    + +
    + +
    +

    Empty field

    - try { - self.features.addData(feature); - } catch (except) { - wrongSoFar += 1; - var msg = 'Wrong geometry value'; - if (except.message) msg += ' (' + except.message + ')'; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: msg, category:'error'}); - } - } - } else { - wrongSoFar += 1; - if (wrongSoFar <= 10) { - self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'}); - } - } - return true; - }); - },
  • Private: Remove one or n features from the map

      _remove: function(docs){
    +            
    + +
            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 +link this Leaflet layer to a Recline doc

    - var self = this; +
    + +
              cid: doc.cid
    +        };
     
    -    if (!(docs instanceof Array)) docs = [docs];
    +        try {
    +          self.features.addData(feature);
    +        } catch (except) {
    +          wrongSoFar += 1;
    +          var msg = 'Wrong geometry value';
    +          if (except.message) msg += ' (' + except.message + ')';
    +          if (wrongSoFar <= 10) {
    +            self.trigger('recline:flash', {message: msg, category:'error'});
    +          }
    +        }
    +      } else {
    +        wrongSoFar += 1;
    +        if (wrongSoFar <= 10) {
    +          self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'});
    +        }
    +      }
    +      return true;
    +    });
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Remove one or n features from the map

    - _.each(docs,function(doc){ - for (var key in self.features._layers){ - if (self.features._layers[key].feature.properties.cid == doc.cid){ - self.features.removeLayer(self.features._layers[key]); - } - } - }); +
    + +
      _remove: function(docs){
     
    -  },
  • Private: convert DMS coordinates to decimal

    + var self = this; -

    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'));
    -      if (typeof(value) === 'string'){

    We may have a GeoJSON string representation

            try {
    -          value = $.parseJSON(value);
    -        } catch(e) {}
    -      }
    -      if (typeof(value) === 'string') {
    -        value = value.replace('(', '').replace(')', '');
    -        var parts = value.split(',');
    -        var lat = this._parseCoordinateString(parts[0]);
    -        var lon = this._parseCoordinateString(parts[1]);
    +    if (!(docs instanceof Array)) docs = [docs];
     
    -        if (!isNaN(lon) && !isNaN(parseFloat(lat))) {
    -          return {
    -            "type": "Point",
    -            "coordinates": [lon, lat]
    -          };
    -        } else {
    -          return null;
    -        }
    -      } else if (value && _.isArray(value)) {

    [ lon, lat ]

            return {
    -          "type": "Point",
    -          "coordinates": [value[0], value[1]]
    -        };
    -      } else if (value && value.lat) {

    of form { lat: ..., lon: ...}

            return {
    -          "type": "Point",
    -          "coordinates": [value.lon || value.lng, value.lat]
    -        };
    -      }

    We o/w assume that contents of the field are a valid GeoJSON object

          return value;
    -    } else if (this.state.get('lonField') && this.state.get('latField')){

    We'll create a GeoJSON like point object from the two lat/lon fields

          var lon = doc.get(this.state.get('lonField'));
    -      var lat = doc.get(this.state.get('latField'));
    -      lon = this._parseCoordinateString(lon);
    -      lat = this._parseCoordinateString(lat);
    +    _.each(docs,function(doc){
    +      for (var key in self.features._layers){
    +        if (self.features._layers[key].feature.geometry.properties.cid == doc.cid){
    +          self.features.removeLayer(self.features._layers[key]);
    +        }
    +      }
    +    });
     
    -      if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) {
    -        return {
    -          type: 'Point',
    -          coordinates: [lon,lat]
    -        };
    -      }
    -    }
    -    return null;
    -  },

    Private: Check if there is a field with GeoJSON geometries or alternatively, + }, + + + + +

  • +
    + +
    + +
    +

    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'));
    +      if (typeof(value) === 'string'){
    + +
  • + + +
  • +
    + +
    + +
    +

    We may have a GeoJSON string representation

    + +
    + +
            try {
    +          value = $.parseJSON(value);
    +        } catch(e) {}
    +      }
    +      if (typeof(value) === 'string') {
    +        value = value.replace('(', '').replace(')', '');
    +        var parts = value.split(',');
    +        var lat = this._parseCoordinateString(parts[0]);
    +        var lon = this._parseCoordinateString(parts[1]);
    +
    +        if (!isNaN(lon) && !isNaN(parseFloat(lat))) {
    +          return {
    +            "type": "Point",
    +            "coordinates": [lon, lat]
    +          };
    +        } else {
    +          return null;
    +        }
    +      } else if (value && _.isArray(value)) {
    + +
  • + + +
  • +
    + +
    + +
    +

    [ lon, lat ]

    + +
    + +
            return {
    +          "type": "Point",
    +          "coordinates": [value[0], value[1]]
    +        };
    +      } else if (value && value.lat) {
    + +
  • + + +
  • +
    + +
    + +
    +

    of form { lat: …, lon: …}

    + +
    + +
            return {
    +          "type": "Point",
    +          "coordinates": [value.lon || value.lng, value.lat]
    +        };
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    We o/w assume that contents of the field are a valid GeoJSON object

    + +
    + +
          return value;
    +    } else if (this.state.get('lonField') && this.state.get('latField')){
    + +
  • + + +
  • +
    + +
    + +
    +

    We’ll create a GeoJSON like point object from the two lat/lon fields

    + +
    + +
          var lon = doc.get(this.state.get('lonField'));
    +      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',
    +          coordinates: [lon,lat]
    +        };
    +      }
    +    }
    +    return null;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Check if there is a field with GeoJSON geometries or alternatively, two fields with lat/lon values.

    +

    If not found, the user can define them via the UI form.

    -

    If not found, the user can define them via the UI form.

  •   _setupGeometryField: function(){

    should not overwrite if we have already set this (e.g. explicitly via state)

        if (!this._geomReady()) {
    -      this.state.set({
    -        geomField: this._checkField(this.geometryFieldNames),
    -        latField: this._checkField(this.latitudeFieldNames),
    -        lonField: this._checkField(this.longitudeFieldNames)
    -      });
    -      this.menu.state.set(this.state.toJSON());
    -    }
    -  },

    Private: Check if a field in the current model exists in the provided -list of names.

      _checkField: function(fieldNames){
    -    var field;
    -    var modelFieldNames = this.model.fields.pluck('id');
    -    for (var i = 0; i < fieldNames.length; i++){
    -      for (var j = 0; j < modelFieldNames.length; j++){
    -        if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase())
    -          return modelFieldNames[j];
    -      }
    -    }
    -    return null;
    -  },

    Private: Zoom to map to current features extent if any, or to the full -extent if none.

      _zoomToFeatures: function(){
    -    var bounds = this.features.getBounds();
    -    if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){
    -      this.map.fitBounds(bounds);
    -    } else {
    -      this.map.setView([0, 0], 2);
    -    }
    -  },

    Private: Sets up the Leaflet map control and the features layer.

    + + +
      _setupGeometryField: function(){
    + + + + +
  • +
    + +
    + +
    +

    should not overwrite if we have already set this (e.g. explicitly via state)

    +
    + +
        if (!this._geomReady()) {
    +      this.state.set({
    +        geomField: this._checkField(this.geometryFieldNames),
    +        latField: this._checkField(this.latitudeFieldNames),
    +        lonField: this._checkField(this.longitudeFieldNames)
    +      });
    +      this.menu.state.set(this.state.toJSON());
    +    }
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Check if a field in the current model exists in the provided +list of names.

    + +
    + +
      _checkField: function(fieldNames){
    +    var field;
    +    var modelFieldNames = this.model.fields.pluck('id');
    +    for (var i = 0; i < fieldNames.length; i++){
    +      for (var j = 0; j < modelFieldNames.length; j++){
    +        if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase())
    +          return modelFieldNames[j];
    +      }
    +    }
    +    return null;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Zoom to map to current features extent if any, or to the full +extent if none.

    + +
    + +
      _zoomToFeatures: function(){
    +    var bounds = this.features.getBounds();
    +    if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){
    +      this.map.fitBounds(bounds);
    +    } else {
    +      this.map.setView([0, 0], 2);
    +    }
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Private: Sets up the Leaflet map control and the features layer.

    The map uses a base layer from MapQuest based -on OpenStreetMap.

  •   _setupMap: function(){
    -    var self = this;
    -    this.map = new L.Map(this.$map.get(0));
    +on OpenStreetMap.

    - var mapUrl = "//otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"; - var osmAttribution = 'Map data &copy; 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); +
    + +
      _setupMap: function(){
    +    var self = this;
    +    this.map = new L.Map(this.$map.get(0));
     
    -    this.markers = new L.MarkerClusterGroup(this._clusterOptions);

    rebind this (as needed in e.g. default case above)

        this.geoJsonLayerOptions.pointToLayer =  _.bind(
    -        this.geoJsonLayerOptions.pointToLayer,
    -        this);
    -    this.features = new L.GeoJSON(null, this.geoJsonLayerOptions);
    +    var mapUrl = "http://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png";
    +    var osmAttribution = 'Map data &copy; 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 bg = new L.TileLayer(mapUrl, {maxZoom: 18, attribution: osmAttribution ,subdomains: '1234'});
    +    this.map.addLayer(bg);
     
    -    this.map.setView([0, 0], 2);
    +    this.markers = new L.MarkerClusterGroup(this._clusterOptions);
    + + + + +
  • +
    + +
    + +
    +

    rebind this (as needed in e.g. default case above)

    - this.mapReady = true; - },
  • Private: Helper function to select an option from a select list

      _selectOption: function(id,value){
    -    var options = $('.' + id + ' > select > option');
    -    if (options){
    -      options.each(function(opt){
    -        if (this.value == value) {
    -          $(this).attr('selected','selected');
    -          return false;
    -        }
    -      });
    -    }
    -  }
    -});
    +            
    + +
        this.geoJsonLayerOptions.pointToLayer =  _.bind(
    +        this.geoJsonLayerOptions.pointToLayer,
    +        this);
    +    this.features = new L.GeoJSON(null, this.geoJsonLayerOptions);
     
    -my.MapMenu = Backbone.View.extend({
    -  className: 'editor',
    +    this.map.setView([0, 0], 2);
     
    -  template: ' \
    -    <form class="form-stacked"> \
    -      <div class="clearfix"> \
    -        <div class="editor-field-type"> \
    -            <label class="radio"> \
    -              <input type="radio" id="editor-field-type-latlon" name="editor-field-type" value="latlon" checked="checked"/> \
    -              Latitude / Longitude fields</label> \
    -            <label class="radio"> \
    -              <input type="radio" id="editor-field-type-geom" name="editor-field-type" value="geom" /> \
    -              GeoJSON field</label> \
    -        </div> \
    -        <div class="editor-field-type-latlon"> \
    -          <label>Latitude field</label> \
    -          <div class="input editor-lat-field"> \
    -            <select> \
    -            <option value=""></option> \
    -            {{#fields}} \
    -            <option value="{{id}}">{{label}}</option> \
    -            {{/fields}} \
    -            </select> \
    -          </div> \
    -          <label>Longitude field</label> \
    -          <div class="input editor-lon-field"> \
    -            <select> \
    -            <option value=""></option> \
    -            {{#fields}} \
    -            <option value="{{id}}">{{label}}</option> \
    -            {{/fields}} \
    -            </select> \
    -          </div> \
    -        </div> \
    -        <div class="editor-field-type-geom" style="display:none"> \
    -          <label>Geometry field (GeoJSON)</label> \
    -          <div class="input editor-geom-field"> \
    -            <select> \
    -            <option value=""></option> \
    -            {{#fields}} \
    -            <option value="{{id}}">{{label}}</option> \
    -            {{/fields}} \
    -            </select> \
    -          </div> \
    -        </div> \
    -      </div> \
    -      <div class="editor-buttons"> \
    -        <button class="btn editor-update-map">Update</button> \
    -      </div> \
    -      <div class="editor-options" > \
    -        <label class="checkbox"> \
    -          <input type="checkbox" id="editor-auto-zoom" value="autozoom" checked="checked" /> \
    -          Auto zoom to features</label> \
    -        <label class="checkbox"> \
    -          <input type="checkbox" id="editor-cluster" value="cluster"/> \
    -          Cluster markers</label> \
    -      </div> \
    -      <input type="hidden" class="editor-id" value="map-1" /> \
    -    </form> \
    -  ',

    Define here events for UI elements

      events: {
    -    'click .editor-update-map': 'onEditorSubmit',
    -    'change .editor-field-type': 'onFieldTypeChange',
    -    'click #editor-auto-zoom': 'onAutoZoomChange',
    -    'click #editor-cluster': 'onClusteringChange'
    -  },
    +    this.mapReady = true;
    +  },
    + + + + +
  • +
    + +
    + +
    +

    Private: Helper function to select an option from a select list

    - initialize: function(options) { - var self = this; - _.bindAll(this, 'render'); - this.listenTo(this.model.fields, 'change', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.listenTo(this.state, 'change', this.render); - this.render(); - },
  • Public: Adds the necessary elements to the page.

    + + +
      _selectOption: function(id,value){
    +    var options = $('.' + id + ' > select > option');
    +    if (options){
    +      options.each(function(opt){
    +        if (this.value == value) {
    +          $(this).attr('selected','selected');
    +          return false;
    +        }
    +      });
    +    }
    +  }
    +});
     
    -

    Also sets up the editor fields and the map if necessary.

      render: function() {
    -    var self = this;
    -    var htmls = Mustache.render(this.template, this.model.toTemplateJSON());
    -    this.$el.html(htmls);
    +my.MapMenu = Backbone.View.extend({
    +  className: 'editor',
     
    -    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();
    -      } 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();
    -      }
    -    }
    -    if (this.state.get('autoZoom')) {
    -      this.$el.find('#editor-auto-zoom').attr('checked', 'checked');
    -    } else {
    -      this.$el.find('#editor-auto-zoom').removeAttr('checked');
    -    }
    -    if (this.state.get('cluster')) {
    -      this.$el.find('#editor-cluster').attr('checked', 'checked');
    -    } else {
    -      this.$el.find('#editor-cluster').removeAttr('checked');
    -    }
    -    return this;
    -  },
    +  template: ' \
    +    <form class="form-stacked"> \
    +      <div class="clearfix"> \
    +        <div class="editor-field-type"> \
    +            <label class="radio"> \
    +              <input type="radio" id="editor-field-type-latlon" name="editor-field-type" value="latlon" checked="checked"/> \
    +              Latitude / Longitude fields</label> \
    +            <label class="radio"> \
    +              <input type="radio" id="editor-field-type-geom" name="editor-field-type" value="geom" /> \
    +              GeoJSON field</label> \
    +        </div> \
    +        <div class="editor-field-type-latlon"> \
    +          <label>Latitude field</label> \
    +          <div class="input editor-lat-field"> \
    +            <select class="form-control"> \
    +            <option value=""></option> \
    +            {{#fields}} \
    +            <option value="{{id}}">{{label}}</option> \
    +            {{/fields}} \
    +            </select> \
    +          </div> \
    +          <label>Longitude field</label> \
    +          <div class="input editor-lon-field"> \
    +            <select class="form-control"> \
    +            <option value=""></option> \
    +            {{#fields}} \
    +            <option value="{{id}}">{{label}}</option> \
    +            {{/fields}} \
    +            </select> \
    +          </div> \
    +        </div> \
    +        <div class="editor-field-type-geom" style="display:none"> \
    +          <label>Geometry field (GeoJSON)</label> \
    +          <div class="input editor-geom-field"> \
    +            <select class="form-control"> \
    +            <option value=""></option> \
    +            {{#fields}} \
    +            <option value="{{id}}">{{label}}</option> \
    +            {{/fields}} \
    +            </select> \
    +          </div> \
    +        </div> \
    +      </div> \
    +      <div class="editor-buttons"> \
    +        <button class="btn btn-default editor-update-map">Update</button> \
    +      </div> \
    +      <div class="editor-options" > \
    +        <label class="checkbox"> \
    +          <input type="checkbox" id="editor-auto-zoom" value="autozoom" checked="checked" /> \
    +          Auto zoom to features</label> \
    +        <label class="checkbox"> \
    +          <input type="checkbox" id="editor-cluster" value="cluster"/> \
    +          Cluster markers</label> \
    +      </div> \
    +      <input type="hidden" class="editor-id" value="map-1" /> \
    +    </form> \
    +  ',
    + + + + +
  • +
    + +
    + +
    +

    Define here events for UI elements

    - _geomReady: function() { - return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - },
  • UI Event handlers

    Public: Update map with user options

    + + +
      events: {
    +    'click .editor-update-map': 'onEditorSubmit',
    +    'change .editor-field-type': 'onFieldTypeChange',
    +    'click #editor-auto-zoom': 'onAutoZoomChange',
    +    'click #editor-cluster': 'onClusteringChange'
    +  },
     
    +  initialize: function(options) {
    +    var self = this;
    +    _.bindAll(this, 'render');
    +    this.listenTo(this.model.fields, 'change', this.render);
    +    this.state = new recline.Model.ObjectState(options.state);
    +    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;
    +    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();
    +      } 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();
    +      }
    +    }
    +    if (this.state.get('autoZoom')) {
    +      this.$el.find('#editor-auto-zoom').attr('checked', 'checked');
    +    } else {
    +      this.$el.find('#editor-auto-zoom').removeAttr('checked');
    +    }
    +    if (this.state.get('cluster')) {
    +      this.$el.find('#editor-cluster').attr('checked', 'checked');
    +    } else {
    +      this.$el.find('#editor-cluster').removeAttr('checked');
    +    }
    +    return this;
    +  },
    +
    +  _geomReady: function() {
    +    return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    UI Event handlers

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Public: Update map with user options

    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')){
    -      this.state.set({
    -        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()
    -      });
    -    }
    -    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();
    -    } else {
    -        this.$el.find('.editor-field-type-geom').hide();
    -        this.$el.find('.editor-field-type-latlon').show();
    -    }
    -  },
    +location information.

    - onAutoZoomChange: function(e){ - this.state.set({autoZoom: !this.state.get('autoZoom')}); - }, +
    + +
      onEditorSubmit: function(e){
    +    e.preventDefault();
    +    if (this.$el.find('#editor-field-type-geom').attr('checked')){
    +      this.state.set({
    +        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()
    +      });
    +    }
    +    return false;
    +  },
    + + + + +
  • +
    + +
    + +
    +

    Public: Shows the relevant select lists depending on the location field +type selected.

    - 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');
    -    if (options){
    -      options.each(function(opt){
    -        if (this.value == value) {
    -          $(this).attr('selected','selected');
    -          return false;
    -        }
    -      });
    -    }
    -  }
    -});
    +            
    + +
      onFieldTypeChange: function(e){
    +    if (e.target.value == 'geom'){
    +        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();
    +    }
    +  },
     
    -})(jQuery, recline.View);
    +  onAutoZoomChange: function(e){
    +    this.state.set({autoZoom: !this.state.get('autoZoom')});
    +  },
     
    -
    \ No newline at end of file + 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');
    +    if (options){
    +      options.each(function(opt){
    +        if (this.value == value) {
    +          $(this).attr('selected','selected');
    +          return false;
    +        }
    +      });
    +    }
    +  }
    +});
    +
    +})(jQuery, recline.View);
    + +
  • + + + + + diff --git a/docs/src/view.multiview.html b/docs/src/view.multiview.html index a49cd9ab..070be0b1 100644 --- a/docs/src/view.multiview.html +++ b/docs/src/view.multiview.html @@ -1,13 +1,167 @@ - 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) {
    -  "use strict";

    MultiView

    + + + 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) {
      +  "use strict";
      + +
    • + + +
    • +
      + +
      + +
      +

      MultiView

      Manage multiple views together along with query editor etc. Usage:

      -
      -var myExplorer = new model.recline.MultiView({
      +var myExplorer = new recline.View.MultiView({
         model: {{recline.Model.Dataset instance}}
         el: {{an existing dom element}}
         views: {{dataset views}}
      @@ -15,19 +169,15 @@ var myExplorer = new model.recline.MultiView({
       });
       
      -

      Parameters

      - +

      Parameters

      model: (required) recline.model.Dataset instance.

      -

      el: (required) DOM element to bind to. NB: the element already being in the DOM is important for rendering of some subviews (e.g. Graph).

      -

      views: (optional) the dataset views (Grid, Graph etc) for MultiView to show. This is an array of view hashes. If not provided initialize with (recline.View.)Grid, Graph, and Map views (with obvious id and labels!).

      -
       var views = [
         {
      @@ -51,13 +201,12 @@ var views = [
       MultiView to show. This is an array of view hashes. If not provided
       initialize with (recline.View.)FilterEditor and Fields views (with obvious 
       id and labels!).

      -
       var sidebarViews = [
         {
           id: 'filterEditor', // used for routing
           label: 'Filters', // used for view switcher
      -    view: new recline.View.FielterEditor({
      +    view: new recline.View.FilterEditor({
             model: dataset
           })
         },
      @@ -73,12 +222,11 @@ var sidebarViews = [
       
       

      state: standard state config for this view. This state is slightly special as it includes config of many of the subviews.

      -
      -state = {
      +var state = {
           query: {dataset query state - see dataset.queryState object}
      -    view-{id1}: {view-state for this view}
      -    view-{id2}: {view-state for }
      +    'view-{id1}': {view-state for this view}
      +    'view-{id2}': {view-state for }
           ...
           // Explorer
           currentView: id of current view (defaults to first view if not specified)
      @@ -87,404 +235,814 @@ state = {
       

      Note that at present we do not serialize information about the actual set -of views in use -- e.g. those specified by the views argument -- but instead +of views in use — e.g. those specified by the views argument — but instead expect either that the default views are fine or that the client to have -initialized the MultiView with the relevant views themselves.

    my.MultiView = Backbone.View.extend({
    -  template: ' \
    -  <div class="recline-data-explorer"> \
    -    <div class="alert-messages"></div> \
    -    \
    -    <div class="header clearfix"> \
    -      <div class="navigation"> \
    -        <div class="btn-group" data-toggle="buttons-radio"> \
    -        {{#views}} \
    -        <a href="#{{id}}" data-view="{{id}}" class="btn">{{label}}</a> \
    -        {{/views}} \
    -        </div> \
    -      </div> \
    -      <div class="recline-results-info"> \
    -        <span class="doc-count">{{recordCount}}</span> records\
    -      </div> \
    -      <div class="menu-right"> \
    -        <div class="btn-group" data-toggle="buttons-checkbox"> \
    -          {{#sidebarViews}} \
    -          <a href="#" data-action="{{id}}" class="btn">{{label}}</a> \
    -          {{/sidebarViews}} \
    -        </div> \
    -      </div> \
    -      <div class="query-editor-here" style="display:inline;"></div> \
    -    </div> \
    -    <div class="data-view-sidebar"></div> \
    -    <div class="data-view-container"></div> \
    -  </div> \
    -  ',
    -  events: {
    -    'click .menu-right a': '_onMenuClick',
    -    'click .navigation a': '_onSwitchView'
    -  },
    +initialized the MultiView with the relevant views themselves.

    - initialize: function(options) { - var self = this; - 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 {
    -      this.pageViews = [{
    -        id: 'grid',
    -        label: 'Grid',
    -        view: new my.SlickGrid({
    -          model: this.model,
    -          state: this.state.get('view-grid')
    -        })
    -      }, {
    -        id: 'graph',
    -        label: 'Graph',
    -        view: new my.Graph({
    -          model: this.model,
    -          state: this.state.get('view-graph')
    -        })
    -      }, {
    -        id: 'map',
    -        label: 'Map',
    -        view: new my.Map({
    -          model: this.model,
    -          state: this.state.get('view-map')
    -        })
    -      }, {
    -        id: 'timeline',
    -        label: 'Timeline',
    -        view: new my.Timeline({
    -          model: this.model,
    -          state: this.state.get('view-timeline')
    -        })
    -      }];
    -    }

    Hashes of sidebar elements

        if(options.sidebarViews) {
    -      this.sidebarViews = options.sidebarViews;
    -    } else {
    -      this.sidebarViews = [{
    -        id: 'filterEditor',
    -        label: 'Filters',
    -        view: new my.FilterEditor({
    -          model: this.model
    -        })
    -      }, {
    -        id: 'fieldsView',
    -        label: 'Fields',
    -        view: new my.Fields({
    -          model: this.model
    -        })
    -      }];
    -    }

    these must be called after pageViews are created

        this.render();
    -    this._bindStateChanges();
    -    this._bindFlashNotifications();

    now do updates based on state (need to come after render)

        if (this.state.get('readOnly')) {
    -      this.setReadOnly();
    -    }
    -    if (this.state.get('currentView')) {
    -      this.updateNav(this.state.get('currentView'));
    -    } else {
    -      this.updateNav(this.pageViews[0].id);
    -    }
    -    this._showHideSidebar();
    +            
    + +
    my.MultiView = Backbone.View.extend({
    +  template: ' \
    +  <div class="recline-data-explorer"> \
    +    <div class="alert-messages"></div> \
    +    \
    +    <div class="header clearfix"> \
    +      <div class="navigation"> \
    +        <div class="btn-group" data-toggle="buttons-radio"> \
    +        {{#views}} \
    +        <button href="#{{id}}" data-view="{{id}}" class="btn btn-default">{{label}}</button> \
    +        {{/views}} \
    +        </div> \
    +      </div> \
    +      <div class="recline-results-info"> \
    +        <span class="doc-count">{{recordCount}}</span> records\
    +      </div> \
    +      <div class="menu-right"> \
    +        <div class="btn-group" data-toggle="buttons-checkbox"> \
    +          {{#sidebarViews}} \
    +          <button href="#" data-action="{{id}}" class="btn btn-default">{{label}}</button> \
    +          {{/sidebarViews}} \
    +        </div> \
    +      </div> \
    +      <div class="query-editor-here" style="display:inline;"></div> \
    +    </div> \
    +    <div class="data-view-sidebar"></div> \
    +    <div class="data-view-container"></div> \
    +  </div> \
    +  ',
    +  events: {
    +    'click .menu-right button': '_onMenuClick',
    +    'click .navigation button': '_onSwitchView'
    +  },
     
    -    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 + ': ';
    -        }
    -        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 + initialize: function(options) { + var self = this; + 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 {
    +      this.pageViews = [{
    +        id: 'grid',
    +        label: 'Grid',
    +        view: new my.SlickGrid({
    +          model: this.model,
    +          state: this.state.get('view-grid')
    +        })
    +      }, {
    +        id: 'graph',
    +        label: 'Graph',
    +        view: new my.Graph({
    +          model: this.model,
    +          state: this.state.get('view-graph')
    +        })
    +      }, {
    +        id: 'map',
    +        label: 'Map',
    +        view: new my.Map({
    +          model: this.model,
    +          state: this.state.get('view-map')
    +        })
    +      }, {
    +        id: 'timeline',
    +        label: 'Timeline',
    +        view: new my.Timeline({
    +          model: this.model,
    +          state: this.state.get('view-timeline')
    +        })
    +      }];
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Hashes of sidebar elements

    + +
    + +
        if(options.sidebarViews) {
    +      this.sidebarViews = options.sidebarViews;
    +    } else {
    +      this.sidebarViews = [{
    +        id: 'filterEditor',
    +        label: 'Filters',
    +        view: new my.FilterEditor({
    +          model: this.model
    +        })
    +      }, {
    +        id: 'fieldsView',
    +        label: 'Fields',
    +        view: new my.Fields({
    +          model: this.model
    +        })
    +      }];
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    these must be called after pageViews are created

    + +
    + +
        this.render();
    +    this._bindStateChanges();
    +    this._bindFlashNotifications();
    + +
  • + + +
  • +
    + +
    + +
    +

    now do updates based on state (need to come after render)

    + +
    + +
        if (this.state.get('readOnly')) {
    +      this.setReadOnly();
    +    }
    +    if (this.state.get('currentView')) {
    +      this.updateNav(this.state.get('currentView'));
    +    } else {
    +      this.updateNav(this.pageViews[0].id);
    +    }
    +    this._showHideSidebar();
    +
    +    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 + ': ';
    +        }
    +        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});
    -  },
    +TODO: set query state …?

    - setReadOnly: function() { - this.$el.addClass('recline-read-only'); - }, +
    + +
        this.model.queryState.set(self.state.get('query'), {silent: true});
    +  },
     
    -  render: function() {
    -    var tmplData = this.model.toTemplateJSON();
    -    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) {
    -      view.view.render();
    -      $dataViewContainer.append(view.view.el);
    -      if (view.view.elSidebar) {
    -        $dataSidebar.append(view.view.elSidebar);
    -      }
    -    });
    +  setReadOnly: function() {
    +    this.$el.addClass('recline-read-only');
    +  },
     
    -    _.each(this.sidebarViews, function(view) {
    -      this['$'+view.id] = view.view.$el;
    -      $dataSidebar.append(view.view.el);
    -    }, this);
    +  render: function() {
    +    var tmplData = this.model.toTemplateJSON();
    +    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

    - this.pager = new recline.View.Pager({ - model: this.model - }); - this.$el.find('.recline-results-info').after(this.pager.el); +
    + +
        var $dataViewContainer = this.$el.find('.data-view-container');
    +    var $dataSidebar = this.$el.find('.data-view-sidebar');
    + +
  • + + +
  • +
    + +
    + +
    +

    the main views

    - this.queryEditor = new recline.View.QueryEditor({ - model: this.model.queryState - }); - this.$el.find('.query-editor-here').append(this.queryEditor.el); +
    + +
        _.each(this.pageViews, function(view, pageName) {
    +      view.view.render();
    +      if (view.view.redraw) {
    +        view.view.redraw();
    +      }
    +      $dataViewContainer.append(view.view.el);
    +      if (view.view.elSidebar) {
    +        $dataSidebar.append(view.view.elSidebar);
    +      }
    +    });
     
    -  },
    +    _.each(this.sidebarViews, function(view) {
    +      this['$'+view.id] = view.view.$el;
    +      $dataSidebar.append(view.view.el);
    +    }, this);
     
    -  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 visibleChildren = $dataSidebar.children().filter(function() {
    -      return $(this).css("display") != "none";
    -    }).length;
    +    this.pager = new recline.View.Pager({
    +      model: this.model
    +    });
    +    this.$el.find('.recline-results-info').after(this.pager.el);
     
    -    if (visibleChildren > 0) {
    -      $dataSidebar.show();
    -    } else {
    -      $dataSidebar.hide();
    -    }
    -  },
    +    this.queryEditor = new recline.View.QueryEditor({
    +      model: this.model.queryState
    +    });
    +    this.$el.find('.query-editor-here').append(this.queryEditor.el);
     
    -  updateNav: function(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();
    -        if (view.view.elSidebar) {
    -          view.view.elSidebar.show();
    -        }
    -      } else {
    -        view.view.$el.hide();
    -        if (view.view.elSidebar) {
    -          view.view.elSidebar.hide();
    -        }
    -        if (view.view.hide) {
    -          view.view.hide();
    -        }
    -      }
    -    });
    +  },
     
    -    this._showHideSidebar();

    call view.view.show after sidebar visibility has been determined so -that views can correctly calculate their maximum width

        _.each(this.pageViews, function(view, idx) {
    -      if (view.id === pageName) {
    -        if (view.view.show) {
    -          view.view.show();
    -        }
    -      }
    -    });
    -  },
    +  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

    - _onMenuClick: function(e) { - e.preventDefault(); - var action = $(e.target).attr('data-action'); - this['$'+action].toggle(); - this._showHideSidebar(); - }, +
    + +
      _showHideSidebar: function() {
    +    var $dataSidebar = this.$el.find('.data-view-sidebar');
    +    var visibleChildren = $dataSidebar.children().filter(function() {
    +      return $(this).css("display") != "none";
    +    }).length;
     
    -  _onSwitchView: function(e) {
    -    e.preventDefault();
    -    var viewName = $(e.target).attr('data-view');
    -    this.updateNav(viewName);
    -    this.state.set({currentView: viewName});
    -  },
  • create a state object for this view and do the job of

    + if (visibleChildren > 0) { + $dataSidebar.show(); + } else { + $dataSidebar.hide(); + } + }, + updateNav: function(pageName) { + this.$el.find('.navigation button').removeClass('active'); + var $el = this.$el.find('.navigation button[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();
    +        if (view.view.elSidebar) {
    +          view.view.elSidebar.show();
    +        }
    +      } else {
    +        view.view.$el.hide();
    +        if (view.view.elSidebar) {
    +          view.view.elSidebar.hide();
    +        }
    +        if (view.view.hide) {
    +          view.view.hide();
    +        }
    +      }
    +    });
    +
    +    this._showHideSidebar();
    + +
  • + + +
  • +
    + +
    + +
    +

    call view.view.show after sidebar visibility has been determined so +that views can correctly calculate their maximum width

    + +
    + +
        _.each(this.pageViews, function(view, idx) {
    +      if (view.id === pageName) {
    +        if (view.view.show) {
    +          view.view.show();
    +        }
    +      }
    +    });
    +  },
    +
    +  _onMenuClick: function(e) {
    +    e.preventDefault();
    +    var action = $(e.target).attr('data-action');
    +    this['$'+action].toggle();
    +    this._showHideSidebar();
    +  },
    +
    +  _onSwitchView: function(e) {
    +    e.preventDefault();
    +    var viewName = $(e.target).attr('data-view');
    +    this.updateNav(viewName);
    +    this.state.set({currentView: viewName});
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    create a state object for this view and do the job of

    a) initializing it from both data passed in and other sources (e.g. hash url)

    +

    b) ensure the state object is updated in responese to changes in subviews, query etc.

    -

    b) ensure the state object is updated in responese to changes in subviews, query etc.

  •   _setupState: function(initialState) {
    -    var self = this;

    get data from the query string / hash url plus some defaults

        var qs = my.parseHashQueryString();
    -    var query = qs.reclineQuery;
    -    query = query ? JSON.parse(query) : self.model.queryState.toJSON();

    backwards compatability (now named view-graph but was named graph)

        var graphState = qs['view-graph'] || qs.graph;
    -    graphState = graphState ? JSON.parse(graphState) : {};

    now get default data + hash url plus initial state and initial our state object with it

        var stateData = _.extend({
    -        query: query,
    -        'view-graph': graphState,
    -        backend: this.model.backend.__type__,
    -        url: this.model.get('url'),
    -        dataset: this.model.toJSON(),
    -        currentView: null,
    -        readOnly: false
    -      },
    -      initialState);
    -    this.state = new recline.Model.ObjectState(stateData);
    -  },
    +            
    + +
      _setupState: function(initialState) {
    +    var self = this;
    + + + + +
  • +
    + +
    + +
    +

    get data from the query string / hash url plus some defaults

    - _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.listenTo(this.model.queryState, 'change', function() {
    -      self.state.set({query: self.model.queryState.toJSON()});
    -    });
    -    _.each(this.pageViews, function(pageView) {
    -      if (pageView.view.state && pageView.view.state.bind) {
    -        var update = {};
    -        update['view-' + pageView.id] = pageView.view.state.toJSON();
    -        self.state.set(update);
    -        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');
    -        });
    -      }
    -    });
    -  },
    +            
    + +
        var qs = my.parseHashQueryString();
    +    var query = qs.reclineQuery;
    +    query = query ? JSON.parse(query) : self.model.queryState.toJSON();
    + + + + +
  • +
    + +
    + +
    +

    backwards compatability (now named view-graph but was named graph)

    - _bindFlashNotifications: function() { - var self = this; - _.each(this.pageViews, function(pageView) { - self.listenTo(pageView.view, 'recline:flash', function(flash) { - self.notify(flash); - }); - }); - },
  • notify

    + + +
        var graphState = qs['view-graph'] || qs.graph;
    +    graphState = graphState ? JSON.parse(graphState) : {};
    + + + + +
  • +
    + +
    + +
    +

    now get default data + hash url plus initial state and initial our state object with it

    +
    + +
        var stateData = _.extend({
    +        query: query,
    +        'view-graph': graphState,
    +        backend: this.model.backend.__type__,
    +        url: this.model.get('url'),
    +        dataset: this.model.toJSON(),
    +        currentView: null,
    +        readOnly: false
    +      },
    +      initialState);
    +    this.state = new recline.Model.ObjectState(stateData);
    +  },
    +
    +  _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.listenTo(this.model.queryState, 'change', function() {
    +      self.state.set({query: self.model.queryState.toJSON()});
    +    });
    +    _.each(this.pageViews, function(pageView) {
    +      if (pageView.view.state && pageView.view.state.bind) {
    +        var update = {};
    +        update['view-' + pageView.id] = pageView.view.state.toJSON();
    +        self.state.set(update);
    +        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');
    +        });
    +      }
    +    });
    +  },
    +
    +  _bindFlashNotifications: function() {
    +    var self = this;
    +    _.each(this.pageViews, function(pageView) {
    +      self.listenTo(pageView.view, 'recline:flash', function(flash) {
    +        self.notify(flash);
    +      });
    +    });
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    notify

    Create a notification (a div.alert in div.alert-messsages) using provided flash object. Flash attributes (all are optional):

    -
    • message: message to show.
    • category: warning (default), success, error
    • persist: if true alert is persistent, o/w hidden after 3s (default = false)
    • loader: if true show loading spinner
    • -
  •   notify: function(flash) {
    -    var tmplData = _.extend({
    -      message: 'Loading',
    -      category: 'warning',
    -      loader: false
    -      },
    -      flash
    -    );
    -    var _template;
    -    if (tmplData.loader) {
    -      _template = ' \
    -        <div class="alert alert-info alert-loader"> \
    -          {{message}} \
    -          <span class="notification-loader">&nbsp;</span> \
    -        </div>';
    -    } else {
    -      _template = ' \
    -        <div class="alert alert-{{category}} fade in" data-alert="alert"><a class="close" data-dismiss="alert" href="#">×</a> \
    -          {{message}} \
    -        </div>';
    -    }
    -    var _templated = $(Mustache.render(_template, tmplData));
    -    _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages'));
    -    if (!flash.persist) {
    -      setTimeout(function() {
    -        $(_templated).fadeOut(1000, function() {
    -          $(this).remove();
    -        });
    -      }, 1000);
    -    }
    -  },

    clearNotifications

    + -

    Clear all existing notifications

      clearNotifications: function() {
    -    var $notifications = $('.recline-data-explorer .alert-messages .alert');
    -    $notifications.fadeOut(1500, function() {
    -      $(this).remove();
    -    });
    -  }
    -});

    MultiView.restore

    + + +
      notify: function(flash) {
    +    var tmplData = _.extend({
    +      message: 'Loading',
    +      category: 'warning',
    +      loader: false
    +      },
    +      flash
    +    );
    +    var _template;
    +    if (tmplData.loader) {
    +      _template = ' \
    +        <div class="alert alert-info alert-loader"> \
    +          {{message}} \
    +          <span class="notification-loader">&nbsp;</span> \
    +        </div>';
    +    } else {
    +      _template = ' \
    +        <div class="alert alert-{{category}} fade in" data-alert="alert"><a class="close" data-dismiss="alert" href="#">×</a> \
    +          {{message}} \
    +        </div>';
    +    }
    +    var _templated = $(Mustache.render(_template, tmplData));
    +    _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages'));
    +    if (!flash.persist) {
    +      setTimeout(function() {
    +        $(_templated).fadeOut(1000, function() {
    +          $(this).remove();
    +        });
    +      }, 1000);
    +    }
    +  },
    + + + + +
  • +
    + +
    + +
    +

    clearNotifications

    +

    Clear all existing notifications

    +
    + +
      clearNotifications: function() {
    +    var $notifications = $('.recline-data-explorer .alert-messages .alert');
    +    $notifications.fadeOut(1500, function() {
    +      $(this).remove();
    +    });
    +  }
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    MultiView.restore

    Restore a MultiView instance from a serialized state including the associated dataset

    +

    This inverts the state serialization process in Multiview

    -

    This inverts the state serialization process in Multiview

  • my.MultiView.restore = function(state) {

    hack-y - restoring a memory dataset does not mean much ... (but useful for testing!)

      var datasetInfo;
    -  if (state.backend === 'memory') {
    -    datasetInfo = {
    -      backend: 'memory',
    -      records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}]
    -    };
    -  } else {
    -    datasetInfo = _.extend({
    -        url: state.url,
    -        backend: state.backend
    -      },
    -      state.dataset
    -    );
    -  }
    -  var dataset = new recline.Model.Dataset(datasetInfo);
    -  var explorer = new my.MultiView({
    -    model: dataset,
    -    state: state
    -  });
    -  return explorer;
    -};

    Miscellaneous Utilities

    var urlPathRegex = /^([^?]+)(\?.*)?/;

    Parse the Hash section of a URL into path and query string

    my.parseHashUrl = function(hashUrl) {
    -  var parsed = urlPathRegex.exec(hashUrl);
    -  if (parsed === null) {
    -    return {};
    -  } else {
    -    return {
    -      path: parsed[1],
    -      query: parsed[2] || ''
    -    };
    -  }
    -};

    Parse a URL query string (?xyz=abc...) into a dictionary.

    my.parseQueryString = function(q) {
    -  if (!q) {
    -    return {};
    -  }
    -  var urlParams = {},
    -    e, d = function (s) {
    -      return unescape(s.replace(/\+/g, " "));
    -    },
    -    r = /([^&=]+)=?([^&]*)/g;
    +            
    + +
    my.MultiView.restore = function(state) {
    + + + + +
  • +
    + +
    + +
    +

    hack-y - restoring a memory dataset does not mean much … (but useful for testing!)

    - if (q && q.length && q[0] === '?') { - q = q.slice(1); - } - while (e = r.exec(q)) {
  • TODO: have values be array as query string allow repetition of keys

        urlParams[d(e[1])] = d(e[2]);
    -  }
    -  return urlParams;
    -};

    Parse the query string out of the URL hash

    my.parseHashQueryString = function() {
    -  var q = my.parseHashUrl(window.location.hash).query;
    -  return my.parseQueryString(q);
    -};

    Compse a Query String

    my.composeQueryString = function(queryParams) {
    -  var queryString = '?';
    -  var items = [];
    -  $.each(queryParams, function(key, value) {
    -    if (typeof(value) === 'object') {
    -      value = JSON.stringify(value);
    -    }
    -    items.push(key + '=' + encodeURIComponent(value));
    -  });
    -  queryString += items.join('&');
    -  return queryString;
    -};
    +            
    + +
      var datasetInfo;
    +  if (state.backend === 'memory') {
    +    datasetInfo = {
    +      backend: 'memory',
    +      records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}]
    +    };
    +  } else {
    +    datasetInfo = _.extend({
    +        url: state.url,
    +        backend: state.backend
    +      },
    +      state.dataset
    +    );
    +  }
    +  var dataset = new recline.Model.Dataset(datasetInfo);
    +  var explorer = new my.MultiView({
    +    model: dataset,
    +    state: state
    +  });
    +  return explorer;
    +};
    + + + + +
  • +
    + +
    + +
    +

    Miscellaneous Utilities

    -my.getNewHashForQueryString = function(queryParams) { - var queryPart = my.composeQueryString(queryParams); - if (window.location.hash) {
  • slice(1) to remove # at start

        return window.location.hash.split('?')[0].slice(1) + queryPart;
    -  } else {
    -    return queryPart;
    -  }
    -};
    +            
    + +
    var urlPathRegex = /^([^?]+)(\?.*)?/;
    + + + + +
  • +
    + +
    + +
    +

    Parse the Hash section of a URL into path and query string

    -my.setHashQueryString = function(queryParams) { - window.location.hash = my.getNewHashForQueryString(queryParams); -}; +
    + +
    my.parseHashUrl = function(hashUrl) {
    +  var parsed = urlPathRegex.exec(hashUrl);
    +  if (parsed === null) {
    +    return {};
    +  } else {
    +    return {
    +      path: parsed[1],
    +      query: parsed[2] || ''
    +    };
    +  }
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    Parse a URL query string (?xyz=abc…) into a dictionary.

    -})(jQuery, recline.View); +
    + +
    my.parseQueryString = function(q) {
    +  if (!q) {
    +    return {};
    +  }
    +  var urlParams = {},
    +    e, d = function (s) {
    +      return unescape(s.replace(/\+/g, " "));
    +    },
    +    r = /([^&=]+)=?([^&]*)/g;
     
    -
  • \ No newline at end of file + if (q && q.length && q[0] === '?') { + q = q.slice(1); + } + while (e = r.exec(q)) { + + + + +
  • +
    + +
    + +
    +

    TODO: have values be array as query string allow repetition of keys

    + +
    + +
        urlParams[d(e[1])] = d(e[2]);
    +  }
    +  return urlParams;
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    Parse the query string out of the URL hash

    + +
    + +
    my.parseHashQueryString = function() {
    +  var q = my.parseHashUrl(window.location.hash).query;
    +  return my.parseQueryString(q);
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    Compse a Query String

    + +
    + +
    my.composeQueryString = function(queryParams) {
    +  var queryString = '?';
    +  var items = [];
    +  $.each(queryParams, function(key, value) {
    +    if (typeof(value) === 'object') {
    +      value = JSON.stringify(value);
    +    }
    +    items.push(key + '=' + encodeURIComponent(value));
    +  });
    +  queryString += items.join('&');
    +  return queryString;
    +};
    +
    +my.getNewHashForQueryString = function(queryParams) {
    +  var queryPart = my.composeQueryString(queryParams);
    +  if (window.location.hash) {
    + +
  • + + +
  • +
    + +
    + +
    +

    slice(1) to remove # at start

    + +
    + +
        return window.location.hash.split('?')[0].slice(1) + queryPart;
    +  } else {
    +    return queryPart;
    +  }
    +};
    +
    +my.setHashQueryString = function(queryParams) {
    +  window.location.hash = my.getNewHashForQueryString(queryParams);
    +};
    +
    +})(jQuery, recline.View);
    + +
  • + + + + + diff --git a/docs/src/view.slickgrid.html b/docs/src/view.slickgrid.html index 110e8ea5..80bb6006 100644 --- a/docs/src/view.slickgrid.html +++ b/docs/src/view.slickgrid.html @@ -1,48 +1,160 @@ - view.slickgrid.js

    view.slickgrid.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.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>',
    +
    +
    +  view.slickgrid.js
    +  
    +  
    +  
    +
    +
    +  
    +
    - initialize: function(options){ - var self = this; - _.bindAll(this, 'render'); - this.state = new recline.Model.ObjectState(); - this.render(); - }, + + +
      + +
    • +
      +

      view.slickgrid.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /*jshint multistr:true */
       
      -    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

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +(function($, my) { + "use strict"; + + + + +
  • +
    + +
    + +
    +

    SlickGrid Dataset View

    Provides a tabular view on a Dataset, based on SlickGrid.

    - -

    https://github.com/mleibman/SlickGrid

    - +

    https://github.com/mleibman/SlickGrid

    Initialize it with a recline.Model.Dataset.

    -

    Additional options to drive SlickGrid grid can be given through state. -The following keys allow for customization: -* gridOptions: to add options at grid level -* columnsEditor: to add editor for editable columns

    - +The following keys allow for customization:

    +
      +
    • gridOptions: to add options at grid level
    • +
    • columnsEditor: to add editor for editable columns
    • +

    For example: var grid = new recline.View.SlickGrid({ model: dataset, @@ -50,377 +162,953 @@ The following keys allow for customization: state: { gridOptions: { editable: true, - enableAddRows: true - ... + enableAddRow: true + // Enable support for row delete + enabledDelRow: true, + // Enable support for row Reorder + enableReOrderRow:true, + … }, columnsEditor: [ - {column: 'date', editor: Slick.Editors.Date }, - {column: 'title', editor: Slick.Editors.Text} + {column: ‘date’, editor: Slick.Editors.Date }, + {column: ‘title’, editor: Slick.Editors.Text} ] } }); -// NB: you need an explicit height on the element for slickgrid to work

  • my.SlickGrid = Backbone.View.extend({
    -  initialize: function(modelEtc) {
    -    var self = this;
    -    this.$el.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: [],
    -        columnsSort: {},
    -        columnsWidth: [],
    -        columnsEditor: [],
    -        options: {},
    -        fitColumns: false
    -      }, modelEtc.state
    +// NB: you need an explicit height on the element for slickgrid to work

    - ); - 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())
    -      });
    -    }
    -  },
    -  onRecordChanged: function(record) {

    Ignore if the grid is not yet drawn

        if (!this.grid) {
    -      return;
    -    }

    Let's find the row corresponding to the index

        var row_index = this.grid.getData().getModelRow( record );
    -    this.grid.invalidateRow(row_index);
    -    this.grid.getData().updateItem(record, row_index);
    -    this.grid.render();
    -  },
    -   render: function() {
    -    var self = this;
    -    var options = _.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

        var columns = []; 

    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) {
    -      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 
    -      }
    -    };

    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,
    -        name: field.label,
    -        field: field.id,
    -        sortable: true,
    -        minWidth: 80,
    -        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 = _.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) {
    -      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 -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) {
    -      var row = {};
    -      self.model.fields.each(function(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;
    -    }
    +            
    + +
    my.SlickGrid = Backbone.View.extend({
    +  initialize: function(modelEtc) {
    +    var self = this;
    +    this.$el.addClass('recline-slickgrid');
    + + + + +
  • +
    + +
    + +
    +

    Template for row delete menu , change it if you don’t love

    - function RowSet() { - var models = []; - var rows = []; +
    + +
        this.templates = {
    +      "deleterow" : '<button href="#" class="recline-row-delete btn btn-default" title="Delete row">X</button>'
    +    };
     
    -      this.push = function(model, row) {
    -        models.push(model);
    -        rows.push(row);
    -      };
    +    _.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: [],
    +        columnsSort: {},
    +        columnsWidth: [],
    +        columnsEditor: [],
    +        options: {},
    +        fitColumns: false
    +      }, modelEtc.state
     
    -      this.getLength = function() {return rows.length; };
    -      this.getItem = function(index) {return rows[index];};
    -      this.getItemMetadata = function(index) {return {};};
    -      this.getModel = function(index) {return models[index];};
    -      this.getModelRow = function(m) {return _.indexOf(models, m);};
    -      this.updateItem = function(m,i) {
    -        rows[i] = toRow(m);
    -        models[i] = m;
    -      };
    -     
    -    }
    +    );
    +    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

    - var data = new RowSet(); +
    + +
        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())
    +      });
    +    }
    +  },
     
    -    this.model.records.each(function(doc){
    -      data.push(doc, toRow(doc));
    -    });
    +  onRecordChanged: function(record) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Ignore if the grid is not yet drawn

    - 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);
    -    }
    +            
    + +
        if (!this.grid) {
    +      return;
    +    }
    + + + + +
  • +
    + +
    + +
    +

    Let’s find the row corresponding to the index

    - this._slickHandler.subscribe(this.grid.onSort, function(e, args){ - var order = (args.sortAsc) ? 'asc':'desc'; - var sort = [{ - field: args.sortCol.field, - order: order - }]; - self.model.query({sort: sort}); - }); +
    + +
        var row_index = this.grid.getData().getModelRow( record );
    +    this.grid.invalidateRow(row_index);
    +    this.grid.getData().updateItem(record, row_index);
    +    this.grid.render();
    +  },
     
    -    this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){
    -      self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')});
    -    });
    +  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

    - this.grid.onColumnsResized.subscribe(function(e, args){ - var columns = args.grid.getColumns(); - var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth; - var columnsWidth = []; - _.each(columns,function(column){ - if (column.width != defaultColumnWidth){ - columnsWidth.push({column:column.id,width:column.width}); - } - }); - self.state.set({columnsWidth:columnsWidth}); - }); +
    + +
        var columns = [];
    + +
  • + + +
  • +
    + +
    + +
    +

    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) {
    +      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 
    +      }
    +    };
    + +
  • + + +
  • +
    + +
    + +
    +

    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 column for row reorder support

    + +
    + +
        if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow == true) {
    +      columns.push({
    +        id: "#",
    +        name: "",
    +        width: 22,
    +        behavior: "selectAndMove",
    +        selectable: false,
    +        resizable: false,
    +        cssClass: "recline-cell-reorder"
    +      })
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Add column for row delete support

    + +
    + +
        if (this.state.get("gridOptions") && this.state.get("gridOptions").enabledDelRow == true) {
    +      columns.push({
    +        id: 'del',
    +        name: '',
    +        field: 'del',
    +        sortable: true,
    +        width: 38,
    +        formatter: formatter,
    +        validator:validator
    +      })
    +    }
    +
    +    function sanitizeFieldName(name) {
    +      var sanitized = $(name).text();
    +      return (name !== sanitized && sanitized !== '') ? sanitized : name;
    +    }
    +
    +    _.each(this.model.fields.toJSON(),function(field){
    +      var column = {
    +        id: field.id,
    +        name: sanitizeFieldName(field.label),
    +        field: field.id,
    +        sortable: true,
    +        minWidth: 80,
    +        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 = _.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) {
    +      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 +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) {
    +      var row = {};
    +      self.model.fields.each(function(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;
    +    }
    +
    +    function RowSet() {
    +      var models = [];
    +      var rows = [];
    +
    +      this.push = function(model, row) {
    +        models.push(model);
    +        rows.push(row);
    +      };
    +
    +      this.getLength = function() {return rows.length; };
    +      this.getItem = function(index) {return rows[index];};
    +      this.getItemMetadata = function(index) {return {};};
    +      this.getModel = function(index) {return models[index];};
    +      this.getModelRow = function(m) {return _.indexOf(models, m);};
    +      this.updateItem = function(m,i) {
    +        rows[i] = toRow(m);
    +        models[i] = m;
    +      };
    +    }
    +
    +    var data = new RowSet();
    +
    +    this.model.records.each(function(doc){
    +      data.push(doc, toRow(doc));
    +    });
    +
    +    this.grid = new Slick.Grid(this.el, data, visibleColumns, options);
    + +
  • + + +
  • +
    + +
    + +
    +

    Column sorting

    + +
    + +
        var sortInfo = this.model.queryState.get('sort');
    +    if (sortInfo){
    +      var column = sortInfo[0].field;
    +      var sortAsc = sortInfo[0].order !== 'desc';
    +      this.grid.setSortColumn(column, sortAsc);
    +    }
    +
    +    if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) {
    +      this._setupRowReordering();
    +    }
         
    -    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);
    -    });  
    -    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()
    -   }
    -     }) ;
    +    this._slickHandler.subscribe(this.grid.onSort, function(e, args){
    +      var order = (args.sortAsc) ? 'asc':'desc';
    +      var sort = [{
    +        field: args.sortCol.field,
    +        order: order
    +      }];
    +      self.model.query({sort: sort});
    +    });
         
    -     var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid,
    -                                                       _.extend(options,{state:this.state}));
    +    this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){
    +      self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')});
    +    });
    +    
    +    this.grid.onColumnsResized.subscribe(function(e, args){
    +        var columns = args.grid.getColumns();
    +        var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth;
    +        var columnsWidth = [];
    +        _.each(columns,function(column){
    +          if (column.width != defaultColumnWidth){
    +            columnsWidth.push({column:column.id,width:column.width});
    +          }
    +        });
    +        self.state.set({columnsWidth:columnsWidth});
    +    });
    +    
    +    this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) {
    + + + + +
  • +
    + +
    + +
    +

    We need to change the model associated value

    - if (self.visible){ - self.grid.init(); - self.rendered = true; - } else {
  • Defer rendering until the view is visible

          self.rendered = false;
    -    }
    +            
    + +
          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);
    +    });  
    +    this._slickHandler.subscribe(this.grid.onClick,function(e, args){
    + + + + +
  • +
    + +
    + +
    +

    try catch , because this fail in qunit , but no +error on browser.

    - return this; - }, +
    + +
          try{e.preventDefault()}catch(e){}
    + +
  • + + +
  • +
    + +
    + +
    +

    The cell of grid that handle row delete is The first cell (0) if +The grid ReOrder is not present ie enableReOrderRow == false +else it is The the second cell (1) , because The 0 is now cell +that handle row Reoder.

    - remove: function () { - this._slickHandler.unsubscribeAll(); - Backbone.View.prototype.remove.apply(this, arguments); - }, +
    + +
          var cell =0
    +      if(self.state.get("gridOptions") 
    +  && self.state.get("gridOptions").enableReOrderRow != undefined 
    +        && self.state.get("gridOptions").enableReOrderRow == true ){
    +        cell =1
    +      }
    +      if (args.cell == cell && self.state.get("gridOptions").enabledDelRow == true){
    + +
  • + + +
  • +
    + +
    + +
    +

    We need to delete the associated model

    - 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();
    -      }
    -      this.grid.init();
    -      this.rendered = true;
    -    }
    -    this.visible = true;
    -  },
    +            
    + +
              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

    - hide: function() { - this.visible = false; - } -}); +
    + +
          self.rendered = false;
    +    }
    +    return this;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    Row reordering support based on +https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example9-row-reordering.html

    -})(jQuery, recline.View); +
    + +
      _setupRowReordering: function() {
    +    var self = this;
    +    self.grid.setSelectionModel(new Slick.RowSelectionModel());
     
    -/*
    -* Context menu for the column picker, adapted from
    -* http://mleibman.github.com/SlickGrid/examples/example-grouping
    -*
    -*/
    -(function ($) {
    -  function SlickColumnPicker(columns, grid, options) {
    -    var $menu;
    -    var columnCheckboxes;
    +    var moveRowsPlugin = new Slick.RowMoveManager({
    +      cancelEditOnDrag: true
    +    });
     
    -    var defaults = {
    -      fadeSpeed:250
    -    };
    +    moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) {
    +      for (var i = 0; i < data.rows.length; i++) {
    + +
  • + + +
  • +
    + +
    + +
    +

    no point in moving before or after itself

    - function init() { - grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); - options = $.extend({}, defaults, options); +
    + +
            if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) {
    +          e.stopPropagation();
    +          return false;
    +        }
    +      }
    +      return true;
    +    });
    +    
    +    moveRowsPlugin.onMoveRows.subscribe(function (e, args) {
    +      var extractedRows = [], left, right;
    +      var rows = args.rows;
    +      var insertBefore = args.insertBefore;
     
    -      $menu = $('<ul class="dropdown-menu slick-contextmenu" style="display:none;position:absolute;z-index:20;" />').appendTo(document.body);
    +      var data = self.model.records.toJSON()      
    +      left = data.slice(0, insertBefore);
    +      right= data.slice(insertBefore, data.length);
    +      
    +      rows.sort(function(a,b) { return a-b; });
     
    -      $menu.bind('mouseleave', function (e) {
    -        $(this).fadeOut(options.fadeSpeed);
    -      });
    -      $menu.bind('click', updateColumn);
    +      for (var i = 0; i < rows.length; i++) {
    +          extractedRows.push(data[rows[i]]);
    +      }
     
    -    }
    +      rows.reverse();
     
    -    function handleHeaderContextMenu(e, args) {
    -      e.preventDefault();
    -      $menu.empty();
    -      columnCheckboxes = [];
    +      for (var i = 0; i < rows.length; i++) {
    +        var row = rows[i];
    +        if (row < insertBefore) {
    +          left.splice(row, 1);
    +        } else {
    +          right.splice(row - insertBefore, 1);
    +        }
    +      }
     
    -      var $li, $input;
    -      for (var i = 0; i < columns.length; i++) {
    -        $li = $('<li />').appendTo($menu);
    -        $input = $('<input type="checkbox" />').data('column-id', columns[i].id).attr('id','slick-column-vis-'+columns[i].id);
    -        columnCheckboxes.push($input);
    +      data = left.concat(extractedRows.concat(right));
    +      var selectedRows = [];
    +      for (var i = 0; i < rows.length; i++)
    +        selectedRows.push(left.length + i);      
     
    -        if (grid.getColumnIndex(columns[i].id) !== null) {
    -          $input.attr('checked', 'checked');
    -        }
    -        $input.appendTo($li);
    -        $('<label />')
    -            .text(columns[i].name)
    -            .attr('for','slick-column-vis-'+columns[i].id)
    -            .appendTo($li);
    -      }
    -      $('<li/>').addClass('divider').appendTo($menu);
    -      $li = $('<li />').data('option', 'autoresize').appendTo($menu);
    -      $input = $('<input type="checkbox" />').data('option', 'autoresize').attr('id','slick-option-autoresize');
    -      $input.appendTo($li);
    -      $('<label />')
    -          .text('Force fit columns')
    -          .attr('for','slick-option-autoresize')
    -          .appendTo($li);
    -      if (grid.getOptions().forceFitColumns) {
    -        $input.attr('checked', 'checked');
    -      }
    +      self.model.records.reset(data)
    +      
    +    });
    + +
  • + + +
  • +
    + +
    + +
    +

    register The plugin to handle row Reorder

    - $menu.css('top', e.pageY - 10) - .css('left', e.pageX - 10) - .fadeIn(options.fadeSpeed); - } +
    + +
        if(this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) {
    +      self.grid.registerPlugin(moveRowsPlugin);
    +    }
    +  },
     
    -    function updateColumn(e) {
    -      var checkbox;
    +  remove: function () {
    +    this._slickHandler.unsubscribeAll();
    +    Backbone.View.prototype.remove.apply(this, arguments);
    +  },
     
    -      if ($(e.target).data('option') === 'autoresize') {
    -        var checked;
    -        if ($(e.target).is('li')){
    -            checkbox = $(e.target).find('input').first();
    -            checked = !checkbox.is(':checked');
    -            checkbox.attr('checked',checked);
    -        } else {
    -          checked = e.target.checked;
    -        }
    +  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 (checked) { - grid.setOptions({forceFitColumns:true}); - grid.autosizeColumns(); - } else { - grid.setOptions({forceFitColumns:false}); - } - options.state.set({fitColumns:checked}); - return; - } +
    + +
        if (!this.rendered){
    +      if (!this.grid){
    +        this.render();
    +      }
    +      this.grid.init();
    +      this.rendered = true;
    +    }
    +    this.visible = true;
    +  },
     
    -      if (($(e.target).is('li') && !$(e.target).hasClass('divider')) ||
    -            $(e.target).is('input')) {
    -        if ($(e.target).is('li')){
    -            checkbox = $(e.target).find('input').first();
    -            checkbox.attr('checked',!checkbox.is(':checked'));
    -        }
    -        var visibleColumns = [];
    -        var hiddenColumnsIds = [];
    -        $.each(columnCheckboxes, function (i, e) {
    -          if ($(this).is(':checked')) {
    -            visibleColumns.push(columns[i]);
    -          } else {
    -            hiddenColumnsIds.push(columns[i].id);
    -          }
    -        });
    +  hide: function() {
    +    this.visible = false;
    +  }
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    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

    - if (!visibleColumns.length) { - $(e.target).attr('checked', 'checked'); - return; - } +
    + +
    my.GridControl= Backbone.View.extend({
    +  className: "recline-row-add",
    + +
  • + + +
  • +
    + +
    + +
    +

    Template for row edit menu , change it if you don’t love

    - grid.setColumns(visibleColumns); - options.state.set({hiddenColumns:hiddenColumnsIds}); - } - } - init(); - }
  • Slick.Controls.ColumnPicker

      $.extend(true, window, { Slick:{ Controls:{ ColumnPicker:SlickColumnPicker }}});
    -})(jQuery);
    +            
    + +
      template: '<h1><button href="#" class="recline-row-add btn btn-default">Add row</button></h1>',
    +  
    +  initialize: function(options){
    +    var self = this;
    +    _.bindAll(this, 'render');
    +    this.state = new recline.Model.ObjectState();
    +    this.render();
    +  },
     
    -
    \ No newline at end of file + 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") + } +}); + +})(jQuery, recline.View); + +/* +* Context menu for the column picker, adapted from +* http://mleibman.github.com/SlickGrid/examples/example-grouping +* +*/ +(function ($) { + function SlickColumnPicker(columns, grid, options) { + var $menu; + var columnCheckboxes; + + var defaults = { + fadeSpeed:250 + }; + + function init() { + grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); + options = $.extend({}, defaults, options); + + $menu = $('<ul class="dropdown-menu slick-contextmenu" style="display:none;position:absolute;z-index:20;" />').appendTo(document.body); + + $menu.bind('mouseleave', function (e) { + $(this).fadeOut(options.fadeSpeed); + }); + $menu.bind('click', updateColumn); + + } + + function handleHeaderContextMenu(e, args) { + e.preventDefault(); + $menu.empty(); + columnCheckboxes = []; + + var $li, $input; + for (var i = 0; i < columns.length; i++) { + $li = $('<li />').appendTo($menu); + $input = $('<input type="checkbox" />').data('column-id', columns[i].id).attr('id','slick-column-vis-'+columns[i].id); + columnCheckboxes.push($input); + + if (grid.getColumnIndex(columns[i].id) !== null) { + $input.attr('checked', 'checked'); + } + $input.appendTo($li); + $('<label />') + .text(columns[i].name) + .attr('for','slick-column-vis-'+columns[i].id) + .appendTo($li); + } + $('<li/>').addClass('divider').appendTo($menu); + $li = $('<li />').data('option', 'autoresize').appendTo($menu); + $input = $('<input type="checkbox" />').data('option', 'autoresize').attr('id','slick-option-autoresize'); + $input.appendTo($li); + $('<label />') + .text('Force fit columns') + .attr('for','slick-option-autoresize') + .appendTo($li); + if (grid.getOptions().forceFitColumns) { + $input.attr('checked', 'checked'); + } + + $menu.css('top', e.pageY - 10) + .css('left', e.pageX - 10) + .fadeIn(options.fadeSpeed); + } + + function updateColumn(e) { + var checkbox; + + if ($(e.target).data('option') === 'autoresize') { + var checked; + if ($(e.target).is('li')){ + checkbox = $(e.target).find('input').first(); + checked = !checkbox.is(':checked'); + checkbox.attr('checked',checked); + } else { + checked = e.target.checked; + } + + if (checked) { + grid.setOptions({forceFitColumns:true}); + grid.autosizeColumns(); + } else { + grid.setOptions({forceFitColumns:false}); + } + options.state.set({fitColumns:checked}); + return; + } + + if (($(e.target).is('li') && !$(e.target).hasClass('divider')) || + $(e.target).is('input')) { + if ($(e.target).is('li')){ + checkbox = $(e.target).find('input').first(); + checkbox.attr('checked',!checkbox.is(':checked')); + } + var visibleColumns = []; + var hiddenColumnsIds = []; + $.each(columnCheckboxes, function (i, e) { + if ($(this).is(':checked')) { + visibleColumns.push(columns[i]); + } else { + hiddenColumnsIds.push(columns[i].id); + } + }); + + if (!visibleColumns.length) { + $(e.target).attr('checked', 'checked'); + return; + } + + grid.setColumns(visibleColumns); + options.state.set({hiddenColumns:hiddenColumnsIds}); + } + } + init(); + } + + + + +
  • +
    + +
    + +
    +

    Slick.Controls.ColumnPicker

    + +
    + +
      $.extend(true, window, {
    +    Slick: {
    +      Controls: {
    +        ColumnPicker: SlickColumnPicker
    +      }
    +    }
    +  });
    +
    +})(jQuery);
    + +
  • + + + + + diff --git a/docs/src/view.timeline.html b/docs/src/view.timeline.html index f86f6c45..5a7551f8 100644 --- a/docs/src/view.timeline.html +++ b/docs/src/view.timeline.html @@ -1,162 +1,446 @@ - view.timeline.js

    view.timeline.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  view.timeline.js
    +  
    +  
    +  
    +
    +
    +  

    turn off unnecessary logging from VMM Timeline

    if (typeof VMM !== 'undefined') {
    -  VMM.debug = false;
    -}

    Timeline

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; -

    Timeline view using http://timeline.verite.co/

    my.Timeline = Backbone.View.extend({
    -  template: ' \
    -    <div class="recline-timeline"> \
    -      <div id="vmm-timeline-id"></div> \
    -    </div> \
    -  ',

    These are the default (case-insensitive) names of field that are used if found. -If not found, the user will need to define these fields on initialization

      startFieldNames: ['date','startdate', 'start', 'start-date'],
    -  endFieldNames: ['end','endDate'],
    -  elementId: '#vmm-timeline-id',
    +(function($, my) {
    +  "use strict";
    + + + + +
  • +
    + +
    + +
    +

    turn off unnecessary logging from VMM Timeline

    - initialize: function(options) { - var self = this; - this.timeline = new VMM.Timeline(this.elementId); - this._timelineIsInitialized = false; - this.listenTo(this.model.fields, 'reset', function() { - self._setupTemporalField(); - }); - this.listenTo(this.model.records, 'all', function() { - self.reloadData(); - }); - var stateData = _.extend({ - startField: 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
    -    );
    -    this.state = new recline.Model.ObjectState(stateData);
    -    this._setupTemporalField();
    -  },
    +            
    + +
    if (typeof VMM !== 'undefined') {
    +  VMM.debug = false;
    +}
    + + + + +
  • +
    + +
    + +
    +

    Timeline

    +

    Timeline view using http://timeline.verite.co/

    - 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 $ -internally to look up element

        if ($(this.elementId).length > 0) {
    -      this._initTimeline();
    -    }
    -  },
    +            
    + +
    my.Timeline = Backbone.View.extend({
    +  template: ' \
    +    <div class="recline-timeline"> \
    +      <div id="vmm-timeline-id"></div> \
    +    </div> \
    +  ',
    + + + + +
  • +
    + +
    + +
    +

    These are the default (case-insensitive) names of field that are used if found. +If not found, the user will need to define these fields on initialization

    - show: function() {
  • only call _initTimeline once view in DOM as Timeline uses $ internally to look up element

        if (this._timelineIsInitialized === false) {
    -      this._initTimeline();
    -    }
    -  },
    +            
    + +
      startFieldNames: ['date','startdate', 'start', 'start-date'],
    +  endFieldNames: ['end','endDate'],
    +  elementId: '#vmm-timeline-id',
     
    -  _initTimeline: function() {
    -    var data = this._timelineJSON();
    -    var config = this.state.get("timelineJSOptions");
    -    config.id = this.elementId;
    -    this.timeline.init(config, data);
    -    this._timelineIsInitialized = true
    -  },
    +  initialize: function(options) {
    +    var self = this;
    +    this.timeline = new VMM.Timeline(this.elementId);
    +    this._timelineIsInitialized = false;
    +    this.listenTo(this.model.fields, 'reset', function() {
    +      self._setupTemporalField();
    +    });
    +    this.listenTo(this.model.records, 'all', function() {
    +      self.reloadData();
    +    });
    +    var stateData = _.extend({
    +        startField: 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

    - reloadData: function() { - if (this._timelineIsInitialized) { - var data = this._timelineJSON(); - this.timeline.reload(data); - } - },
  • Convert record to JSON for timeline

    + + +
            nonUSDates: false,
    +        timelineJSOptions: {}
    +      },
    +      options.state
    +    );
    +    this.state = new recline.Model.ObjectState(stateData);
    +    this._setupTemporalField();
    +  },
     
    -

    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) {
    -    var start = this._parseDate(record.get(this.state.get('startField')));
    -    var end = this._parseDate(record.get(this.state.get('endField')));
    -    if (start) {
    -      var tlEntry = {
    -        "startDate": start,
    -        "endDate": end,
    -        "headline": String(record.get('title') || ''),
    -        "text": record.get('description') || record.summary()
    -      };
    -      return tlEntry;
    -    } else {
    -      return null;
    -    }
    -  },
    +  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 $ +internally to look up element

    - _timelineJSON: function() { - var self = this; - var out = { - 'timeline': { - 'type': 'default', - 'headline': '', - 'date': [ - ] - } - }; - this.model.records.each(function(record) { - var newEntry = self.convertRecord(record, self.fields); - 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) {
    -      var tlEntry = {
    -        "startDate": '2000,1,1',
    -        "headline": 'No data to show!'
    -      };
    -      out.timeline.date.push(tlEntry);
    -    }
    -    return out;
    -  },

    convert dates into a format TimelineJS will handle + + +

        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) {
    +      this._initTimeline();
    +    }
    +  },
    +
    +  _initTimeline: function() {
    +    var data = this._timelineJSON();
    +    var config = this.state.get("timelineJSOptions");
    +    config.id = this.elementId;
    +    this.timeline.init(config, data);
    +    this._timelineIsInitialized = true
    +  },
    +
    +  reloadData: function() {
    +    if (this._timelineIsInitialized) {
    +      var data = this._timelineJSON();
    +      this.timeline.reload(data);
    +    }
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    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) {
    +    var start = this._parseDate(record.get(this.state.get('startField')));
    +    var end = this._parseDate(record.get(this.state.get('endField')));
    +    if (start) {
    +      var tlEntry = {
    +        "startDate": start,
    +        "endDate": end,
    +        "headline": String(record.get('title') || ''),
    +        "text": record.get('description') || record.summary(),
    +        "tag": record.get('tags')
    +      };
    +      return tlEntry;
    +    } else {
    +      return null;
    +    }
    +  },
    +
    +  _timelineJSON: function() {
    +    var self = this;
    +    var out = {
    +      'timeline': {
    +        'type': 'default',
    +        'headline': '',
    +        'date': [
    +        ]
    +      }
    +    };
    +    this.model.records.each(function(record) {
    +      var newEntry = self.convertRecord(record, self.fields);
    +      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) {
    +      var tlEntry = {
    +        "startDate": '2000,1,1',
    +        "headline": 'No data to show!'
    +      };
    +      out.timeline.date.push(tlEntry);
    +    }
    +    return out;
    +  },
    + +
  • + + +
  • +
    + +
    + +
    +

    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 = $.trim(date);
    -    out = out.replace(/(\d)th/g, '$1');
    -    out = out.replace(/(\d)st/g, '$1');
    -    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;
    -  },
    +Supported: mm/dd/yyyy

    - _setupTemporalField: function() { - this.state.set({ - startField: this._checkField(this.startFieldNames), - endField: this._checkField(this.endFieldNames) - }); - }, +
    + +
      _parseDate: function(date) {
    +    if (!date) {
    +      return null;
    +    }
    +    var out = $.trim(date);
    +    out = out.replace(/(\d)th/g, '$1');
    +    out = out.replace(/(\d)st/g, '$1');
    +    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;
    +  },
     
    -  _checkField: function(possibleFieldNames) {
    -    var modelFieldNames = this.model.fields.pluck('id');
    -    for (var i = 0; i < possibleFieldNames.length; i++){
    -      for (var j = 0; j < modelFieldNames.length; j++){
    -        if (modelFieldNames[j].toLowerCase() == possibleFieldNames[i].toLowerCase())
    -          return modelFieldNames[j];
    -      }
    -    }
    -    return null;
    -  }
    -});
    +  _setupTemporalField: function() {
    +    this.state.set({
    +      startField: this._checkField(this.startFieldNames),
    +      endField: this._checkField(this.endFieldNames)
    +    });
    +  },
     
    -})(jQuery, recline.View);
    +  _checkField: function(possibleFieldNames) {
    +    var modelFieldNames = this.model.fields.pluck('id');
    +    for (var i = 0; i < possibleFieldNames.length; i++){
    +      for (var j = 0; j < modelFieldNames.length; j++){
    +        if (modelFieldNames[j].toLowerCase() == possibleFieldNames[i].toLowerCase())
    +          return modelFieldNames[j];
    +      }
    +    }
    +    return null;
    +  }
    +});
     
    -
    \ No newline at end of file +})(jQuery, recline.View); + + + + + + + diff --git a/docs/src/widget.facetviewer.html b/docs/src/widget.facetviewer.html index ddc3ab72..d2523dc3 100644 --- a/docs/src/widget.facetviewer.html +++ b/docs/src/widget.facetviewer.html @@ -1,83 +1,259 @@ - widget.facetviewer.js

    widget.facetviewer.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  widget.facetviewer.js
    +  
    +  
    +  
    +
    +
    +  

    FacetViewer

    +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +(function($, my) { + "use strict"; + + + + +
  • +
    + +
    + +
    +

    FacetViewer

    Widget for displaying facets

    -

    Usage:

    - -
     var viewer = new FacetViewer({
    +
     var viewer = new FacetViewer({
        model: dataset
      });
    -
  • my.FacetViewer = Backbone.View.extend({
    -  className: 'recline-facet-viewer', 
    -  template: ' \
    -    <div class="facets"> \
    -      {{#facets}} \
    -      <div class="facet-summary" data-facet="{{id}}"> \
    -        <h3> \
    -          {{id}} \
    -        </h3> \
    -        <ul class="facet-items"> \
    -        {{#terms}} \
    -          <li><a class="facet-choice js-facet-filter" data-value="{{term}}" href="#{{term}}">{{term}} ({{count}})</a></li> \
    -        {{/terms}} \
    -        {{#entries}} \
    -          <li><a class="facet-choice js-facet-filter" data-value="{{time}}">{{term}} ({{count}})</a></li> \
    -        {{/entries}} \
    -        </ul> \
    -      </div> \
    -      {{/facets}} \
    -    </div> \
    -  ',
    +
    +
    + +
    my.FacetViewer = Backbone.View.extend({
    +  className: 'recline-facet-viewer', 
    +  template: ' \
    +    <div class="facets"> \
    +      {{#facets}} \
    +      <div class="facet-summary" data-facet="{{id}}"> \
    +        <h3> \
    +          {{id}} \
    +        </h3> \
    +        <ul class="facet-items"> \
    +        {{#terms}} \
    +          <li><a class="facet-choice js-facet-filter" data-value="{{term}}" href="#{{term}}">{{term}} ({{count}})</a></li> \
    +        {{/terms}} \
    +        {{#entries}} \
    +          <li><a class="facet-choice js-facet-filter" data-value="{{time}}">{{term}} ({{count}})</a></li> \
    +        {{/entries}} \
    +        </ul> \
    +      </div> \
    +      {{/facets}} \
    +    </div> \
    +  ',
     
    -  events: {
    -    'click .js-facet-filter': 'onFacetFilter'
    -  },
    -  initialize: function(model) {
    -    _.bindAll(this, 'render');
    -    this.listenTo(this.model.facets, 'all', this.render);
    -    this.listenTo(this.model.fields, 'all', this.render);
    -    this.render();
    -  },
    -  render: function() {
    -    var tmplData = {
    -      fields: this.model.fields.toJSON()
    -    };
    -    tmplData.facets = _.map(this.model.facets.toJSON(), function(facet) {
    -      if (facet._type === 'date_histogram') {
    -        facet.entries = _.map(facet.entries, function(entry) {
    -          entry.term = new Date(entry.time).toDateString();
    -          return entry;
    -        });
    -      }
    -      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();
    -    } else {
    -      this.$el.hide();
    -    }
    -  },
    -  onHide: function(e) {
    -    e.preventDefault();
    -    this.$el.hide();
    -  },
    -  onFacetFilter: function(e) {
    -    e.preventDefault();
    -    var $target= $(e.target);
    -    var fieldId = $target.closest('.facet-summary').attr('data-facet');
    -    var value = $target.attr('data-value');
    -    this.model.queryState.addFilter({type: 'term', field: fieldId, term: value});

    have to trigger explicitly for some reason

        this.model.query();
    -  }
    -});
    +  events: {
    +    'click .js-facet-filter': 'onFacetFilter'
    +  },
    +  initialize: function(model) {
    +    _.bindAll(this, 'render');
    +    this.listenTo(this.model.facets, 'all', this.render);
    +    this.listenTo(this.model.fields, 'all', this.render);
    +    this.render();
    +  },
    +  render: function() {
    +    var tmplData = {
    +      fields: this.model.fields.toJSON()
    +    };
    +    tmplData.facets = _.map(this.model.facets.toJSON(), function(facet) {
    +      if (facet._type === 'date_histogram') {
    +        facet.entries = _.map(facet.entries, function(entry) {
    +          entry.term = new Date(entry.time).toDateString();
    +          return entry;
    +        });
    +      }
    +      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();
    +    } else {
    +      this.$el.hide();
    +    }
    +  },
    +  onHide: function(e) {
    +    e.preventDefault();
    +    this.$el.hide();
    +  },
    +  onFacetFilter: function(e) {
    +    e.preventDefault();
    +    var $target= $(e.target);
    +    var fieldId = $target.closest('.facet-summary').attr('data-facet');
    +    var value = $target.attr('data-value');
    +    this.model.queryState.addFilter({type: 'term', field: fieldId, term: value});
    + +
  • + + +
  • +
    + +
    + +
    +

    have to trigger explicitly for some reason

    + +
    + +
        this.model.query();
    +  }
    +});
     
     
    -})(jQuery, recline.View);
    -
    -
  • \ No newline at end of file +})(jQuery, recline.View); + + + + + + + diff --git a/docs/src/widget.fields.html b/docs/src/widget.fields.html index d4fe2282..07b34852 100644 --- a/docs/src/widget.fields.html +++ b/docs/src/widget.fields.html @@ -1,82 +1,299 @@ - 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

      +

      Id / Label / type / format

      -

      Id / Label / type / format

    Editor -- to change type (and possibly format) -Editor for show/hide ...

    Summaries of fields

    + + + + + +
  • +
    + +
    + +
    +

    Editor — to change type (and possibly format) +Editor for show/hide …

    +
    + +
  • + + +
  • +
    + +
    + +
    +

    Summaries of fields

    Top values / number empty -If number: max, min average ...

  • Box to boot transform editor ...

    this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +If number: max, min average …

    -(function($, my) { - "use strict"; +
    + + + + +
  • +
    + +
    + +
    +

    Box to boot transform editor …

    + +
    + +
    +this.recline = this.recline || {};
    +this.recline.View = this.recline.View || {};
    +
    +(function($, my) {
    +  "use strict";
       
    -my.Fields = Backbone.View.extend({
    -  className: 'recline-fields-view', 
    -  template: ' \
    -    <div class="accordion fields-list well"> \
    -    <h3>Fields <a href="#" class="js-show-hide">+</a></h3> \
    -    {{#fields}} \
    -      <div class="accordion-group field"> \
    -        <div class="accordion-heading"> \
    -          <i class="icon-file"></i> \
    -          <h4> \
    -            {{label}} \
    -            <small> \
    -              {{type}} \
    -              <a class="accordion-toggle" data-toggle="collapse" href="#collapse{{id}}"> &raquo; </a> \
    -            </small> \
    -          </h4> \
    -        </div> \
    -        <div id="collapse{{id}}" class="accordion-body collapse"> \
    -          <div class="accordion-inner"> \
    -            {{#facets}} \
    -            <div class="facet-summary" data-facet="{{id}}"> \
    -              <ul class="facet-items"> \
    -              {{#terms}} \
    -                <li class="facet-item"><span class="term">{{term}}</span> <span class="count">[{{count}}]</span></li> \
    -              {{/terms}} \
    -              </ul> \
    -            </div> \
    -            {{/facets}} \
    -            <div class="clear"></div> \
    -          </div> \
    -        </div> \
    -      </div> \
    -    {{/fields}} \
    -    </div> \
    -  ',
    +my.Fields = Backbone.View.extend({
    +  className: 'recline-fields-view', 
    +  template: ' \
    +    <div class="panel-group fields-list well"> \
    +    <h3>Fields <a href="#" class="js-show-hide">+</a></h3> \
    +    {{#fields}} \
    +      <div class="panel panel-default field"> \
    +        <div class="panel-heading"> \
    +          <i class="glyphicon glyphicon-file"></i> \
    +          <h4> \
    +            {{label}} \
    +            <small> \
    +              {{type}} \
    +              <a class="accordion-toggle" data-toggle="collapse" href="#collapse{{id}}"> &raquo; </a> \
    +            </small> \
    +          </h4> \
    +        </div> \
    +        <div id="collapse{{id}}" class="panel-collapse collapse"> \
    +          <div class="panel-body"> \
    +            {{#facets}} \
    +            <div class="facet-summary" data-facet="{{id}}"> \
    +              <ul class="facet-items"> \
    +              {{#terms}} \
    +                <li class="facet-item"><span class="term">{{term}}</span> <span class="count">[{{count}}]</span></li> \
    +              {{/terms}} \
    +              </ul> \
    +            </div> \
    +            {{/facets}} \
    +            <div class="clear"></div> \
    +          </div> \
    +        </div> \
    +      </div> \
    +    {{/fields}} \
    +    </div> \
    +  ',
     
    -  initialize: function(model) {
    -    var self = this;
    -    _.bindAll(this, 'render');
  • TODO: this is quite restrictive in terms of when it is re-run + initialize: function(model) { + var self = this; + _.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.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.render();
    -  },
    -  render: function() {
    -    var self = this;
    -    var tmplData = {
    -      fields: []
    -    };
    -    this.model.fields.each(function(field) {
    -      var out = field.toJSON();
    -      out.facets = field.facets.toJSON();
    -      tmplData.fields.push(out);
    -    });
    -    var templated = Mustache.render(this.template, tmplData);
    -    this.$el.html(templated);
    -  }
    -});
    +being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width)

    -})(jQuery, recline.View); +
    + +
        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

    -
  • \ No newline at end of file + + +
          self.model.getFieldsSummary();
    +      self.render();
    +    });
    +    this.$el.find('.collapse').collapse();
    +    this.render();
    +  },
    +  render: function() {
    +    var self = this;
    +    var tmplData = {
    +      fields: []
    +    };
    +    this.model.fields.each(function(field) {
    +      var out = field.toJSON();
    +      out.facets = field.facets.toJSON();
    +      tmplData.fields.push(out);
    +    });
    +    var templated = Mustache.render(this.template, tmplData);
    +    this.$el.html(templated);
    +  }
    +});
    +
    +})(jQuery, recline.View);
    + + + + + + + diff --git a/docs/src/widget.filtereditor.html b/docs/src/widget.filtereditor.html index d4bf97bb..a761e3d7 100644 --- a/docs/src/widget.filtereditor.html +++ b/docs/src/widget.filtereditor.html @@ -1,168 +1,330 @@ - widget.filtereditor.js

    widget.filtereditor.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  widget.filtereditor.js
    +  
    +  
    +  
    +
    +
    +  
    +
    + + + +
      + +
    • +
      +

      widget.filtereditor.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /*jshint multistr:true */
       
      -(function($, my) {
      -  "use strict";
      +this.recline = this.recline || {};
      +this.recline.View = this.recline.View || {};
       
      -my.FilterEditor = Backbone.View.extend({
      -  className: 'recline-filter-editor well', 
      -  template: ' \
      -    <div class="filters"> \
      -      <h3>Filters</h3> \
      -      <a href="#" class="js-add-filter">Add filter</a> \
      -      <form class="form-stacked js-add" style="display: none;"> \
      -        <fieldset> \
      -          <label>Field</label> \
      -          <select class="fields"> \
      -            {{#fields}} \
      -            <option value="{{id}}">{{label}}</option> \
      -            {{/fields}} \
      -          </select> \
      -          <label>Filter type</label> \
      -          <select class="filterType"> \
      -            <option value="term">Value</option> \
      -            <option value="range">Range</option> \
      -            <option value="geo_distance">Geo distance</option> \
      -          </select> \
      -          <button type="submit" class="btn">Add</button> \
      -        </fieldset> \
      -      </form> \
      -      <form class="form-stacked js-edit"> \
      -        {{#filters}} \
      -          {{{filterRender}}} \
      -        {{/filters}} \
      -        {{#filters.length}} \
      -        <button type="submit" class="btn">Update</button> \
      -        {{/filters.length}} \
      -      </form> \
      -    </div> \
      -  ',
      -  filterTemplates: {
      -    term: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <input type="text" value="{{term}}" name="term" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -        </fieldset> \
      -      </div> \
      -    ',
      -    range: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <label class="control-label" for="">From</label> \
      -          <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="{{to}}" name="to" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -        </fieldset> \
      -      </div> \
      -    ',
      -    geo_distance: ' \
      -      <div class="filter-{{type}} filter"> \
      -        <fieldset> \
      -          <legend> \
      -            {{field}} <small>{{type}}</small> \
      -            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      -          </legend> \
      -          <label class="control-label" for="">Longitude</label> \
      -          <input type="text" value="{{point.lon}}" name="lon" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          <label class="control-label" for="">Latitude</label> \
      -          <input type="text" value="{{point.lat}}" name="lat" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -          <label class="control-label" for="">Distance (km)</label> \
      -          <input type="text" value="{{distance}}" name="distance" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      -        </fieldset> \
      -      </div> \
      -    '
      -  },
      -  events: {
      -    'click .js-remove-filter': 'onRemoveFilter',
      -    'click .js-add-filter': 'onAddFilterShow',
      -    'submit form.js-edit': 'onTermFiltersUpdate',
      -    'submit form.js-add': 'onAddFilter'
      -  },
      -  initialize: function() {
      -    _.bindAll(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() {
      -    var self = this;
      -    var tmplData = $.extend(true, {}, this.model.queryState.toJSON());

    we will use idx in list as there id ...

        tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
    -      filter.id = idx;
    -      return filter;
    -    });
    -    tmplData.fields = this.model.fields.toJSON();
    -    tmplData.filterRender = function() {
    -      return Mustache.render(self.filterTemplates[this.type], this);
    -    };
    -    var out = Mustache.render(this.template, tmplData);
    -    this.$el.html(out);
    -  },
    -  onAddFilterShow: function(e) {
    -    e.preventDefault();
    -    var $target = $(e.target);
    -    $target.hide();
    -    this.$el.find('form.js-add').show();
    -  },
    -  onAddFilter: function(e) {
    -    e.preventDefault();
    -    var $target = $(e.target);
    -    $target.hide();
    -    var filterType = $target.find('select.filterType').val();
    -    var field      = $target.find('select.fields').val();
    -    this.model.queryState.addFilter({type: filterType, field: field});
    -  },
    -  onRemoveFilter: function(e) {
    -    e.preventDefault();
    -    var $target = $(e.target);
    -    var filterId = $target.attr('data-filter-id');
    -    this.model.queryState.removeFilter(filterId);
    -  },
    -  onTermFiltersUpdate: function(e) {
    -   var self = this;
    -    e.preventDefault();
    -    var filters = self.model.queryState.get('filters');
    -    var $form = $(e.target);
    -    _.each($form.find('input'), function(input) {
    -      var $input = $(input);
    -      var filterType  = $input.attr('data-filter-type');
    -      var fieldId     = $input.attr('data-filter-field');
    -      var filterIndex = parseInt($input.attr('data-filter-id'), 10);
    -      var name        = $input.attr('name');
    -      var value       = $input.val();
    +(function($, my) {
    +  "use strict";
     
    -      switch (filterType) {
    -        case 'term':
    -          filters[filterIndex].term = value;
    -          break;
    -        case 'range':
    -          filters[filterIndex][name] = value;
    -          break;
    -        case 'geo_distance':
    -          if(name === 'distance') {
    -            filters[filterIndex].distance = parseFloat(value);
    -          }
    -          else {
    -            filters[filterIndex].point[name] = parseFloat(value);
    -          }
    -          break;
    -      }
    -    });
    -    self.model.queryState.set({filters: filters, from: 0});
    -    self.model.queryState.trigger('change');
    -  }
    -});
    +my.FilterEditor = Backbone.View.extend({
    +  className: 'recline-filter-editor well', 
    +  template: ' \
    +    <div class="filters"> \
    +      <h3>Filters</h3> \
    +      <a href="#" class="js-add-filter">Add filter</a> \
    +      <form class="form-stacked js-add" style="display: none;"> \
    +        <div class="form-group"> \
    +          <label>Field</label> \
    +          <select class="fields form-control"> \
    +            {{#fields}} \
    +            <option value="{{id}}">{{label}}</option> \
    +            {{/fields}} \
    +          </select> \
    +        </div> \
    +        <div class="form-group"> \
    +          <label>Filter type</label> \
    +          <select class="filterType form-control"> \
    +            <option value="term">Value</option> \
    +            <option value="range">Range</option> \
    +            <option value="geo_distance">Geo distance</option> \
    +          </select> \
    +        </div> \
    +        <button type="submit" class="btn btn-default">Add</button> \
    +      </form> \
    +      <form class="form-stacked js-edit"> \
    +        {{#filters}} \
    +          {{{filterRender}}} \
    +        {{/filters}} \
    +        {{#filters.length}} \
    +        <button type="submit" class="btn btn-default">Update</button> \
    +        {{/filters.length}} \
    +      </form> \
    +    </div> \
    +  ',
    +  filterTemplates: {
    +    term: ' \
    +      <div class="filter-{{type}} filter"> \
    +        <fieldset> \
    +          <legend> \
    +            {{field}} <small>{{type}}</small> \
    +            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
    +          </legend> \
    +          <input class="input-sm" type="text" value="{{term}}" name="term" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +        </fieldset> \
    +      </div> \
    +    ',
    +    range: ' \
    +      <div class="filter-{{type}} filter"> \
    +        <fieldset> \
    +          <legend> \
    +            {{field}} <small>{{type}}</small> \
    +            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
    +          </legend> \
    +          <div class="form-group"> \
    +            <label class="control-label" for="">From</label> \
    +            <input class="input-sm" type="text" value="{{from}}" name="from" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +          </div> \
    +          <div class="form-group"> \
    +            <label class="control-label" for="">To</label> \
    +            <input class="input-sm" type="text" value="{{to}}" name="to" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +          </div> \
    +        </fieldset> \
    +      </div> \
    +    ',
    +    geo_distance: ' \
    +      <div class="filter-{{type}} filter"> \
    +        <fieldset> \
    +          <legend> \
    +            {{field}} <small>{{type}}</small> \
    +            <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
    +          </legend> \
    +          <div class="form-group"> \
    +            <label class="control-label" for="">Longitude</label> \
    +            <input class="input-sm" type="text" value="{{point.lon}}" name="lon" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +          </div> \
    +          <div class="form-group"> \
    +            <label class="control-label" for="">Latitude</label> \
    +            <input class="input-sm" type="text" value="{{point.lat}}" name="lat" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +          </div> \
    +          <div class="form-group"> \
    +            <label class="control-label" for="">Distance (km)</label> \
    +            <input class="input-sm" type="text" value="{{distance}}" name="distance" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
    +          </div> \
    +        </fieldset> \
    +      </div> \
    +    '
    +  },
    +  events: {
    +    'click .js-remove-filter': 'onRemoveFilter',
    +    'click .js-add-filter': 'onAddFilterShow',
    +    'submit form.js-edit': 'onTermFiltersUpdate',
    +    'submit form.js-add': 'onAddFilter'
    +  },
    +  initialize: function() {
    +    _.bindAll(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() {
    +    var self = this;
    +    var tmplData = $.extend(true, {}, this.model.queryState.toJSON());
    + + + + +
  • +
    + +
    + +
    +

    we will use idx in list as there id …

    + +
    + +
        tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
    +      filter.id = idx;
    +      return filter;
    +    });
    +    tmplData.fields = this.model.fields.toJSON();
    +    tmplData.filterRender = function() {
    +      return Mustache.render(self.filterTemplates[this.type], this);
    +    };
    +    var out = Mustache.render(this.template, tmplData);
    +    this.$el.html(out);
    +  },
    +  onAddFilterShow: function(e) {
    +    e.preventDefault();
    +    var $target = $(e.target);
    +    $target.hide();
    +    this.$el.find('form.js-add').show();
    +  },
    +  onAddFilter: function(e) {
    +    e.preventDefault();
    +    var $target = $(e.target);
    +    $target.hide();
    +    var filterType = $target.find('select.filterType').val();
    +    var field      = $target.find('select.fields').val();
    +    this.model.queryState.addFilter({type: filterType, field: field});
    +  },
    +  onRemoveFilter: function(e) {
    +    e.preventDefault();
    +    var $target = $(e.target);
    +    var filterId = $target.attr('data-filter-id');
    +    this.model.queryState.removeFilter(filterId);
    +  },
    +  onTermFiltersUpdate: function(e) {
    +   var self = this;
    +    e.preventDefault();
    +    var filters = self.model.queryState.get('filters');
    +    var $form = $(e.target);
    +    _.each($form.find('input'), function(input) {
    +      var $input = $(input);
    +      var filterType  = $input.attr('data-filter-type');
    +      var fieldId     = $input.attr('data-filter-field');
    +      var filterIndex = parseInt($input.attr('data-filter-id'), 10);
    +      var name        = $input.attr('name');
    +      var value       = $input.val();
    +
    +      switch (filterType) {
    +        case 'term':
    +          filters[filterIndex].term = value;
    +          break;
    +        case 'range':
    +          filters[filterIndex][name] = value;
    +          break;
    +        case 'geo_distance':
    +          if(name === 'distance') {
    +            filters[filterIndex].distance = parseFloat(value);
    +          }
    +          else {
    +            filters[filterIndex].point[name] = parseFloat(value);
    +          }
    +          break;
    +      }
    +    });
    +    self.model.queryState.set({filters: filters, from: 0});
    +    self.model.queryState.trigger('change');
    +  }
    +});
     
     
    -})(jQuery, recline.View);
    -
    -
  • \ No newline at end of file +})(jQuery, recline.View); + + + + + + + diff --git a/docs/src/widget.pager.html b/docs/src/widget.pager.html index c03c3d98..71ab94da 100644 --- a/docs/src/widget.pager.html +++ b/docs/src/widget.pager.html @@ -1,70 +1,223 @@ - widget.pager.js

    widget.pager.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  widget.pager.js
    +  
    +  
    +  
    +
    +
    +  
    +
    + + + +
      + +
    • +
      +

      widget.pager.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /*jshint multistr:true */
       
      -(function($, my) {
      -  "use strict";
      +this.recline = this.recline || {};
      +this.recline.View = this.recline.View || {};
       
      -my.Pager = Backbone.View.extend({
      -  className: 'recline-pager', 
      -  template: ' \
      -    <div class="pagination"> \
      -      <ul> \
      -        <li class="prev action-pagination-update"><a href="">&laquo;</a></li> \
      -        <li class="active"><a><input name="from" type="text" value="{{from}}" /> &ndash; <input name="to" type="text" value="{{to}}" /> </a></li> \
      -        <li class="next action-pagination-update"><a href="">&raquo;</a></li> \
      -      </ul> \
      -    </div> \
      -  ',
      +(function($, my) {
      +  "use strict";
       
      -  events: {
      -    'click .action-pagination-update': 'onPaginationUpdate',
      -    'change input': 'onFormSubmit'
      -  },
      +my.Pager = Backbone.View.extend({
      +  className: 'recline-pager', 
      +  template: ' \
      +    <div class="pagination"> \
      +      <ul class="pagination"> \
      +        <li class="prev action-pagination-update"><a href="" class="btn btn-default">&laquo;</a></li> \
      +        <li class="page-range"><a><label for="from">From</label><input name="from" type="text" value="{{from}}" /> &ndash; <label for="to">To</label><input name="to" type="text" value="{{to}}" /> </a></li> \
      +        <li class="next action-pagination-update"><a href="" class="btn btn-default">&raquo;</a></li> \
      +      </ul> \
      +    </div> \
      +  ',
       
      -  initialize: function() {
      -    _.bindAll(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());
      -    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 = Math.max(currFrom - Math.max(0, size), 1)-1;
      -      updateQuery = newFrom != currFrom;
      -    } else {
      -      newFrom = Math.max(currFrom + size, 1);
      -      updateQuery = (newFrom < this.model.recordCount);
      -    }
      -    if (updateQuery) {
      -      this.model.queryState.set({from: newFrom});
      -    }
      -  },
      -  render: function() {
      -    var tmplData = this.model.toJSON();
      -    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);
      -    return this;
      -  }
      -});
      +  events: {
      +    'click .action-pagination-update': 'onPaginationUpdate',
      +    'change input': 'onFormSubmit'
      +  },
       
      -})(jQuery, recline.View);
      +  initialize: function() {
      +    _.bindAll(this, 'render');
      +    this.listenTo(this.model.queryState, 'change', this.render);
      +    this.render();
      +  },
      +  onFormSubmit: function(e) {
      +    e.preventDefault();
      + +
    • + + +
    • +
      + +
      + +
      +

      filter is 0-based; form is 1-based

      -
    \ No newline at end of file + + +
        var formFrom = parseInt(this.$el.find('input[name="from"]').val())-1; 
    +    var formTo = parseInt(this.$el.find('input[name="to"]').val())-1; 
    +    var maxRecord = this.model.recordCount-1;
    +    if (this.model.queryState.get('from') != formFrom) { // changed from; update from
    +      this.model.queryState.set({from: Math.min(maxRecord, Math.max(formFrom, 0))});
    +    } else if (this.model.queryState.get('to') != formTo) { // change to; update size
    +      var to = Math.min(maxRecord, Math.max(formTo, 0));
    +      this.model.queryState.set({size: Math.min(maxRecord+1, Math.max(to-formFrom+1, 1))});
    +    }
    +  },
    +  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 = Math.max(currFrom - Math.max(0, size), 0);
    +      updateQuery = newFrom != currFrom;
    +    } else {
    +      newFrom = Math.max(currFrom + size, 0);
    +      updateQuery = (newFrom < this.model.recordCount);
    +    }
    +    if (updateQuery) {
    +      this.model.queryState.set({from: newFrom});
    +    }
    +  },
    +  render: function() {
    +    var tmplData = this.model.toJSON();
    +    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);
    +    return this;
    +  }
    +});
    +
    +})(jQuery, recline.View);
    + + + + + + + diff --git a/docs/src/widget.queryeditor.html b/docs/src/widget.queryeditor.html index 79262cf0..37a42d30 100644 --- a/docs/src/widget.queryeditor.html +++ b/docs/src/widget.queryeditor.html @@ -1,44 +1,184 @@ - widget.queryeditor.js

    widget.queryeditor.js

    /*jshint multistr:true */
    +
     
    -this.recline = this.recline || {};
    -this.recline.View = this.recline.View || {};
    +
    +
    +  widget.queryeditor.js
    +  
    +  
    +  
    +
    +
    +  
    +
    + + + +
      + +
    • +
      +

      widget.queryeditor.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /*jshint multistr:true */
       
      -(function($, my) {
      -  "use strict";
      +this.recline = this.recline || {};
      +this.recline.View = this.recline.View || {};
       
      -my.QueryEditor = Backbone.View.extend({
      -  className: 'recline-query-editor', 
      -  template: ' \
      -    <form action="" method="GET" class="form-inline"> \
      -      <div class="input-prepend text-query"> \
      -        <span class="add-on"><i class="icon-search"></i></span> \
      -        <input type="text" name="q" value="{{q}}" class="span2" placeholder="Search data ..." class="search-query" /> \
      -      </div> \
      -      <button type="submit" class="btn">Go &raquo;</button> \
      -    </form> \
      -  ',
      +(function($, my) {
      +  "use strict";
       
      -  events: {
      -    'submit form': 'onFormSubmit'
      -  },
      +my.QueryEditor = Backbone.View.extend({
      +  className: 'recline-query-editor', 
      +  template: ' \
      +    <form action="" method="GET" class="form-inline" role="form"> \
      +      <div class="form-group"> \
      +        <div class="input-group text-query"> \
      +          <div class="input-group-addon"> \
      +            <i class="glyphicon glyphicon-search"></i> \
      +          </div> \
      +          <label>Search</label> \
      +          <input class="form-control search-query" type="text" name="q" value="{{q}}" placeholder="Search data ..."> \
      +        </div> \
      +      </div> \
      +      <button type="submit" class="btn btn-default">Go &raquo;</button> \
      +    </form> \
      +  ',
       
      -  initialize: function() {
      -    _.bindAll(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();
      -    this.model.set({q: query});
      -  },
      -  render: function() {
      -    var tmplData = this.model.toJSON();
      -    var templated = Mustache.render(this.template, tmplData);
      -    this.$el.html(templated);
      -  }
      -});
      +  events: {
      +    'submit form': 'onFormSubmit'
      +  },
       
      -})(jQuery, recline.View);
      +  initialize: function() {
      +    _.bindAll(this, 'render');
      +    this.listenTo(this.model, 'change', this.render);
      +    this.render();
      +  },
      +  onFormSubmit: function(e) {
      +    e.preventDefault();
      +    var query = this.$el.find('.search-query').val();
      +    this.model.set({q: query});
      +  },
      +  render: function() {
      +    var tmplData = this.model.toJSON();
      +    var templated = Mustache.render(this.template, tmplData);
      +    this.$el.html(templated);
      +  }
      +});
       
      -
    \ No newline at end of file +})(jQuery, recline.View); + + + + + + + diff --git a/docs/src/widget.valuefilter.html b/docs/src/widget.valuefilter.html new file mode 100644 index 00000000..1f72bc0f --- /dev/null +++ b/docs/src/widget.valuefilter.html @@ -0,0 +1,264 @@ + + + + + widget.valuefilter.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      widget.valuefilter.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /*jshint multistr:true */
      +
      +this.recline = this.recline || {};
      +this.recline.View = this.recline.View || {};
      +
      +(function($, my) {
      +  "use strict";
      +
      +my.ValueFilter = Backbone.View.extend({
      +  className: 'recline-filter-editor well', 
      +  template: ' \
      +    <div class="filters"> \
      +      <h3>Filters</h3> \
      +      <button class="btn js-add-filter add-filter">Add filter</button> \
      +      <form class="form-stacked js-add" style="display: none;"> \
      +        <fieldset> \
      +          <label>Field</label> \
      +          <select class="fields form-control"> \
      +            {{#fields}} \
      +            <option value="{{id}}">{{label}}</option> \
      +            {{/fields}} \
      +          </select> \
      +          <button type="submit" class="btn">Add</button> \
      +        </fieldset> \
      +      </form> \
      +      <form class="form-stacked js-edit"> \
      +        {{#filters}} \
      +          {{{filterRender}}} \
      +        {{/filters}} \
      +        {{#filters.length}} \
      +        <button type="submit" class="btn update-filter">Update</button> \
      +        {{/filters.length}} \
      +      </form> \
      +    </div> \
      +  ',
      +  filterTemplates: {
      +    term: ' \
      +      <div class="filter-{{type}} filter"> \
      +        <fieldset> \
      +          {{field}} \
      +          <a class="js-remove-filter" href="#" title="Remove this filter" data-filter-id="{{id}}">&times;</a> \
      +          <input type="text" value="{{term}}" name="term" data-filter-field="{{field}}" data-filter-id="{{id}}" data-filter-type="{{type}}" /> \
      +        </fieldset> \
      +      </div> \
      +    '
      +  },
      +  events: {
      +    'click .js-remove-filter': 'onRemoveFilter',
      +    'click .js-add-filter': 'onAddFilterShow',
      +    'submit form.js-edit': 'onTermFiltersUpdate',
      +    'submit form.js-add': 'onAddFilter'
      +  },
      +  initialize: function() {
      +    _.bindAll(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() {
      +    var self = this;
      +    var tmplData = $.extend(true, {}, this.model.queryState.toJSON());
      + +
    • + + +
    • +
      + +
      + +
      +

      we will use idx in list as the id …

      + +
      + +
          tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
      +      filter.id = idx;
      +      return filter;
      +    });
      +    tmplData.fields = this.model.fields.toJSON();
      +    tmplData.filterRender = function() {
      +      return Mustache.render(self.filterTemplates.term, this);
      +    };
      +    var out = Mustache.render(this.template, tmplData);
      +    this.$el.html(out);
      +  },
      +  updateFilter: function(input) {
      +    var self = this;
      +    var filters = self.model.queryState.get('filters');
      +    var $input = $(input);
      +    var filterIndex = parseInt($input.attr('data-filter-id'), 10);
      +    var value = $input.val();
      +    filters[filterIndex].term = value;
      +  },
      +  onAddFilterShow: function(e) {
      +    e.preventDefault();
      +    var $target = $(e.target);
      +    $target.hide();
      +    this.$el.find('form.js-add').show();
      +  },
      +  onAddFilter: function(e) {
      +    e.preventDefault();
      +    var $target = $(e.target);
      +    $target.hide();
      +    var field = $target.find('select.fields').val();
      +    this.model.queryState.addFilter({type: 'term', field: field});
      +  },
      +  onRemoveFilter: function(e) {
      +    e.preventDefault();
      +    var $target = $(e.target);
      +    var filterId = $target.attr('data-filter-id');
      +    this.model.queryState.removeFilter(filterId);
      +  },
      +  onTermFiltersUpdate: function(e) {
      +    var self = this;
      +    e.preventDefault();
      +    var filters = self.model.queryState.get('filters');
      +    var $form = $(e.target);
      +    _.each($form.find('input'), function(input) {
      +      self.updateFilter(input);
      +    });
      +    self.model.queryState.set({filters: filters, from: 0});
      +    self.model.queryState.trigger('change');
      +  }
      +});
      +
      +})(jQuery, recline.View);
      + +
    • + +
    +
    + +