datahub/dist/recline.min.js
2016-12-30 12:27:43 -03:00

3 lines
83 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

this.recline=this.recline||{};this.recline.Backend=this.recline.Backend||{};this.recline.Backend.DataProxy=this.recline.Backend.DataProxy||{};(function(my){"use strict";my.__type__="dataproxy";my.dataproxy_url="//jsonpdataproxy.appspot.com";my.timeout=5e3;var Deferred=typeof jQuery!=="undefined"&&jQuery.Deferred||_.Deferred;my.fetch=function(dataset){var data={url:dataset.url,"max-results":dataset.size||dataset.rows||1e3,type:dataset.format||""};var jqxhr=jQuery.ajax({url:my.dataproxy_url,data:data,dataType:"jsonp"});var dfd=new Deferred;_wrapInTimeout(jqxhr).done(function(results){if(results.error){dfd.reject(results.error)}dfd.resolve({records:results.data,fields:results.fields,useMemoryStore:true})}).fail(function(args){dfd.reject(args)});return dfd.promise()};var _wrapInTimeout=function(ourFunction){var dfd=new Deferred;var timer=setTimeout(function(){dfd.reject({message:"Request Error: Backend did not respond after "+my.timeout/1e3+" seconds"})},my.timeout);ourFunction.done(function(args){clearTimeout(timer);dfd.resolve(args)}).fail(function(args){clearTimeout(timer);dfd.reject(args)});return dfd.promise()}})(this.recline.Backend.DataProxy);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);if(!("indexOf"in Array.prototype)){Array.prototype.indexOf=function(find,i){if(i===undefined)i=0;if(i<0)i+=this.length;if(i<0)i=0;for(var n=this.length;i<n;i++)if(i in this&&this[i]===find)return i;return-1}}if(!("lastIndexOf"in Array.prototype)){Array.prototype.lastIndexOf=function(find,i){if(i===undefined)i=this.length-1;if(i<0)i+=this.length;if(i>this.length-1)i=this.length-1;for(i++;i-- >0;)if(i in this&&this[i]===find)return i;return-1}}if(!("forEach"in Array.prototype)){Array.prototype.forEach=function(action,that){for(var i=0,n=this.length;i<n;i++)if(i in this)action.call(that,this[i],i,this)}}if(!("map"in Array.prototype)){Array.prototype.map=function(mapper,that){var other=new Array(this.length);for(var i=0,n=this.length;i<n;i++)if(i in this)other[i]=mapper.call(that,this[i],i,this);return other}}if(!("filter"in Array.prototype)){Array.prototype.filter=function(filter,that){var other=[],v;for(var i=0,n=this.length;i<n;i++)if(i in this&&filter.call(that,v=this[i],i,this))other.push(v);return other}}if(!("every"in Array.prototype)){Array.prototype.every=function(tester,that){for(var i=0,n=this.length;i<n;i++)if(i in this&&!tester.call(that,this[i],i,this))return false;return true}}if(!("some"in Array.prototype)){Array.prototype.some=function(tester,that){for(var i=0,n=this.length;i<n;i++)if(i in this&&tester.call(that,this[i],i,this))return true;return false}}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='<div class="recline-record-summary">';this.fields.each(function(field){if(field.id!="id"){html+='<div class="'+field.id+'"><strong>'+field.get("label")+"</strong>: "+self.getFieldValue(field)+"</div>"}});html+="</div>";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,'<a href="$1">$1</a>')}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.View=this.recline.View||{};(function($,my){"use strict";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,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(){var areWeVisible=!jQuery.expr.filters.hidden(this.el);if(!areWeVisible||this.model.records.length===0){this.needToRedraw=true;return}if(this.state.get("group")&&this.state.get("series")){var series=this.createSeries();var options=this.getGraphOptions(this.state.attributes.graphType,series[0].data.length);this.plot=$.plot(this.$graph,series,options)}},show:function(){if(this.needToRedraw){this.redraw()}},_toolTip:function(event,pos,item){if(item){if(this.previousTooltipPoint.x!==item.dataIndex||this.previousTooltipPoint.y!==item.seriesIndex){this.previousTooltipPoint.x=item.dataIndex;this.previousTooltipPoint.y=item.seriesIndex;$("#recline-flot-tooltip").remove();var x=item.datapoint[0].toFixed(2),y=item.datapoint[1].toFixed(2);if(this.state.attributes.graphType==="bars"){x=item.datapoint[1].toFixed(2),y=item.datapoint[0].toFixed(2)}var content=_.template("<%= group %> = <%= x %>, <%= series %> = <%= y %>",{group:this.state.attributes.group,x:this._xaxisLabel(x),series:item.series.label,y:y});var xLocation,yLocation;if(this.state.attributes.graphType==="bars"){xLocation=item.pageX+15;yLocation=item.pageY-10}else if(this.state.attributes.graphType==="columns"){xLocation=item.pageX+15;yLocation=item.pageY}else{xLocation=item.pageX+10;yLocation=item.pageY-20}$('<div id="recline-flot-tooltip">'+content+"</div>").css({top:yLocation,left:xLocation}).appendTo("body").fadeIn(200)}}else{$("#recline-flot-tooltip").remove();this.previousTooltipPoint.x=null;this.previousTooltipPoint.y=null}},_xaxisLabel:function(x){if(this._groupFieldIsDateTime()){x=new Date(parseFloat(x)).toLocaleDateString()}else if(this.xvaluesAreIndex){x=parseInt(x,10);x=this.model.records.models[x].get(this.state.attributes.group)}return x},getGraphOptions:function(typeId,numPoints){var self=this;var groupFieldIsDateTime=self._groupFieldIsDateTime();var xaxis={};if(!groupFieldIsDateTime){xaxis.tickFormatter=function(x){var label=self._xaxisLabel(x)||"";if(typeof label!=="string"){label=label.toString()}if(self.state.attributes.graphType!=="bars"&&label.length>10){label=label.slice(0,10)+"..."}return label}}if(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 yaxis={};yaxis.autoscale=true;yaxis.autoscaleMargin=.02;var legend={};legend.position="ne";var grid={};grid.hoverable=true;grid.clickable=true;grid.borderColor="#aaaaaa";grid.borderWidth=1;var optionsPerGraphType={lines:{legend:legend,colors:this.graphColors,lines:{show:true},xaxis:xaxis,yaxis:yaxis,grid:grid},points:{legend:legend,colors:this.graphColors,points:{show:true,hitRadius:5},xaxis:xaxis,yaxis:yaxis,grid:grid},"lines-and-points":{legend:legend,colors:this.graphColors,points:{show:true,hitRadius:5},lines:{show:true},xaxis:xaxis,yaxis:yaxis,grid:grid},bars:{legend:legend,colors:this.graphColors,lines:{show:false},xaxis:yaxis,yaxis:xaxis,grid:grid,bars:{show:true,horizontal:true,shadowSize:0,align:"center",barWidth:.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:.8}}};if(self.state.get("graphOptions")){return _.extend(optionsPerGraphType[typeId],self.state.get("graphOptions"))}else{return optionsPerGraphType[typeId]}},_groupFieldIsDateTime:function(){var xfield=this.model.fields.get(this.state.attributes.group);var xtype=xfield.get("type");var isDateTime=xtype==="date"||xtype==="date-time"||xtype==="time";return isDateTime},createSeries:function(){var self=this;self.xvaluesAreIndex=false;var series=[];var xfield=self.model.fields.get(self.state.attributes.group);var isDateTime=self._groupFieldIsDateTime();_.each(this.state.attributes.series,function(field){var points=[];var fieldLabel=self.model.fields.get(field).get("label");if(isDateTime){var cast=function(x){var _date=moment(String(x));if(_date.isValid()){x=_date.toDate().getTime()}return x}}else{var raw=_.map(self.model.records.models,function(doc,index){return doc.getFieldValueUnrendered(xfield)});if(_.all(raw,function(x){return!isNaN(parseFloat(x))})){var cast=function(x){return parseFloat(x)}}else{self.xvaluesAreIndex=true}}_.each(self.model.records.models,function(doc,index){if(self.xvaluesAreIndex){var x=index}else{var x=cast(doc.getFieldValueUnrendered(xfield))}var yfield=self.model.fields.get(field);var y=parseFloat(doc.getFieldValueUnrendered(yfield));if(self.state.attributes.graphType=="bars"){points.push([y,x])}else{points.push([x,y])}});series.push({data:points,label:fieldLabel,hoverable:true})});return series}});my.FlotControls=Backbone.View.extend({className:"editor",template:' <div class="editor"> <form class="form-stacked"> <div class="clearfix"> <div class="form-group"> <label for ="form-field-type">Graph Type</label> <div class="input editor-type"> <select id="form-field-type" 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 for="field-form-group">Group Column (Axis 1)</label> <div class="input editor-group"> <select id="field-form-group" 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 for="form-field-{{seriesName}}">Series <span>{{seriesName}} (Axis 2)</span> [<a href="#remove" class="action-remove-series">Remove</a>] </label> <div class="input"> <select id="form-field-{{seriesName}}" 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"},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()},render:function(){var self=this;var tmplData=this.model.toTemplateJSON();var htmls=Mustache.render(this.template,tmplData);this.$el.html(htmls);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"))}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},_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)},addSeries:function(idx){var data=_.extend({seriesIndex:idx,seriesName:String.fromCharCode(idx+64+1)},this.model.toTemplateJSON());var htmls=Mustache.render(this.templateSeriesEditor,data);this.$el.find(".editor-series-group").append(htmls);return this},_onAddSeries:function(e){e.preventDefault();this.addSeries(this.state.get("series").length)},removeSeries:function(e){e.preventDefault();var $el=$(e.target);$el.parent().parent().remove();this.onEditorSubmit()}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};this.recline.View.Graph=this.recline.View.Flot;this.recline.View.GraphControls=this.recline.View.FlotControls;this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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)},events:{},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});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()},onHorizontalScroll:function(e){var currentScroll=$(e.target).scrollLeft();this.$el.find(".recline-grid thead tr").scrollLeft(currentScroll)},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> ',toTemplateJSON:function(){var self=this;var modelData=this.model.toJSON();modelData.notEmpty=this.fields.length>0;modelData.fields=this.fields.map(function(field){return field.toJSON()});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();var numFields=this.fields.length;var fullWidth=self.$el.width()-20-10*numFields-this.scrollbarDimensions.width;var width=parseInt(Math.max(50,fullWidth/numFields),10);var remainder=Math.max(fullWidth-numFields*width,0);this.fields.each(function(field,idx){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()});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: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}});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}},render:function(){this.$el.attr("data-id",this.model.id);
var html=Mustache.render(this.template,this.toTemplateJSON());this.$el.html(html);return this},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> ',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)},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})})},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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.Map=Backbone.View.extend({template:' <div class="recline-map"> <div class="panel map"></div> </div> ',latitudeFieldNames:["lat","latitude"],longitudeFieldNames:["lon","longitude"],geometryFieldNames:["geojson","geom","the_geom","geometry","spatial","location","geo","lonlat"],initialize:function(options){var self=this;this.visible=this.$el.is(":visible");this.mapReady=false;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,maxClusterRadius:80,singleMarkerMode:false,skipDuplicateAddTesting:true,animateAddingMarkers:false};this.listenTo(this.model.fields,"change",function(){self._setupGeometryField();self.render()});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},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},geoJsonLayerOptions:{pointToLayer:function(feature,latlng){var marker=new L.Marker(latlng);marker.bindPopup(feature.properties.popupContent);this.markers.addLayer(marker);return marker},onEachFeature:function(feature,layer){if(feature.properties&&feature.properties.popupContent){layer.bindPopup(feature.properties.popupContent)}}},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},redraw:function(action,doc){var self=this;action=action||"refresh";if(!self._geomReady()){self._setupGeometryField()}if(!self.mapReady){self._setupMap()}if(this._geomReady()&&this.mapReady){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();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)}if(this.state.get("cluster")){this.map.addLayer(this.markers)}else{this.map.addLayer(this.features)}if(this.state.get("autoZoom")){if(this.visible){this._zoomToFeatures()}else{this._zoomPending=true}}}},show:function(){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"))},_add:function(docs){var self=this;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){return true}else if(feature instanceof Object){feature.properties={popupContent:self.infobox(doc),cid:doc.cid};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})},_remove:function(docs){var self=this;if(!(docs instanceof Array))docs=[docs];_.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])}}})},_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];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},_getGeometryFromRecord:function(doc){if(this.state.get("geomField")){var value=doc.get(this.state.get("geomField"));if(typeof value==="string"){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)){return{type:"Point",coordinates:[value[0],value[1]]}}else if(value&&value.lat){return{type:"Point",coordinates:[value.lon||value.lng,value.lat]}}return value}else if(this.state.get("lonField")&&this.state.get("latField")){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},_setupGeometryField:function(){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())}},_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},_zoomToFeatures:function(){var bounds=this.features.getBounds();if(bounds&&bounds.getNorthEast()&&bounds.getSouthWest()){this.map.fitBounds(bounds)}else{this.map.setView([0,0],2)}},_setupMap:function(){var self=this;this.map=new L.Map(this.$map.get(0));var mapUrl="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";var osmAttribution='©<a href="http://openstreetmap.org" target="_blank">OpenStreetMap</a> contributors';var bg=new L.TileLayer(mapUrl,{maxZoom:19,attribution:osmAttribution});this.map.addLayer(bg);this.markers=new L.MarkerClusterGroup(this._clusterOptions);this.geoJsonLayerOptions.pointToLayer=_.bind(this.geoJsonLayerOptions.pointToLayer,this);this.features=new L.GeoJSON(null,this.geoJsonLayerOptions);this.map.setView([0,0],2);this.mapReady=true},_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}})}}});my.MapMenu=Backbone.View.extend({className:"editor",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 for="form-field-lat-field">Latitude field</label> <div class="input editor-lat-field"> <select id="form-field-lat-field" class="form-control"> <option value=""></option> {{#fields}} <option value="{{id}}">{{label}}</option> {{/fields}} </select> </div> <label for="form-field-lon-field">Longitude field</label> <div class="input editor-lon-field"> <select id="form-field-lon-field" 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> ',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()},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"))},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},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()}},onAutoZoomChange:function(e){this.state.set({autoZoom:!this.state.get("autoZoom")})},onClusteringChange:function(e){this.state.set({cluster:!this.state.get("cluster")})},_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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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"},initialize:function(options){var self=this;this._setupState(options.state);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")})}]}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})}]}this.render();this._bindStateChanges();this._bindFlashNotifications();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})});this.model.queryState.set(self.state.get("query"),{silent:true})},setReadOnly:function(){this.$el.addClass("recline-read-only")},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);var $dataViewContainer=this.$el.find(".data-view-container");var $dataSidebar=this.$el.find(".data-view-sidebar");_.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);this.pager=new recline.View.Pager({model:this.model});this.$el.find(".recline-results-info").after(this.pager.el);this.queryEditor=new recline.View.QueryEditor({model:this.model.queryState});this.$el.find(".query-editor-here").append(this.queryEditor.el)},remove:function(){_.each(this.pageViews,function(view){view.view.remove()});_.each(this.sidebarViews,function(view){view.view.remove()});this.pager.remove();this.queryEditor.remove();Backbone.View.prototype.remove.apply(this,arguments)},_showHideSidebar:function(){var $dataSidebar=this.$el.find(".data-view-sidebar");var visibleChildren=$dataSidebar.children().filter(function(){return $(this).css("display")!="none"}).length;if(visibleChildren>0){$dataSidebar.show()}else{$dataSidebar.hide()}},updateNav:function(pageName){this.$el.find(".navigation button").removeClass("active");var $el=this.$el.find('.navigation button[data-view="'+pageName+'"]');$el.addClass("active");_.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();_.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})},_setupState:function(initialState){var self=this;var qs=my.parseHashQueryString();var query=qs.reclineQuery;query=query?JSON.parse(query):self.model.queryState.toJSON();var graphState=qs["view-graph"]||qs.graph;graphState=graphState?JSON.parse(graphState):{};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;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();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: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(1e3,function(){$(this).remove()})},1e3)}},clearNotifications:function(){var $notifications=$(".recline-data-explorer .alert-messages .alert");$notifications.fadeOut(1500,function(){$(this).remove()})}});my.MultiView.restore=function(state){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};var urlPathRegex=/^([^?]+)(\?.*)?/;my.parseHashUrl=function(hashUrl){var parsed=urlPathRegex.exec(hashUrl);if(parsed===null){return{}}else{return{path:parsed[1],query:parsed[2]||""}}};my.parseQueryString=function(q){if(!q){return{}}var urlParams={},e,d=function(s){return unescape(s.replace(/\+/g," "))},r=/([^&=]+)=?([^&]*)/g;if(q&&q.length&&q[0]==="?"){q=q.slice(1)}while(e=r.exec(q)){urlParams[d(e[1])]=d(e[2])}return urlParams};my.parseHashQueryString=function(){var q=my.parseHashUrl(window.location.hash).query;return my.parseQueryString(q)};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){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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";my.SlickGrid=Backbone.View.extend({initialize:function(modelEtc){var self=this;this.$el.addClass("recline-slickgrid");this.templates={deleterow:'<button href="#" class="recline-row-delete btn btn-default" title="Delete row">X</button>'};_.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.state=new recline.Model.ObjectState(state);this._slickHandler=new Slick.EventHandler;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){if(!this.grid){return}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"));var columns=[];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}};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}}}};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"})}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;try{sanitized=$(name).text()}catch(e){sanitized=""}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{var typeToEditorMap={string:Slick.Editors.LongText,integer:Slick.Editors.IntegerEditor,number:Slick.Editors.Text,date:Slick.Editors.Text,boolean:Slick.Editors.YesNoSelectEditor};if(field.type in typeToEditorMap){column.editor=typeToEditorMap[field.type]}else{column.editor=Slick.Editors.LongText}}columns.push(column)});var visibleColumns=_.filter(columns,function(column){return _.indexOf(self.state.get("hiddenColumns"),column.id)===-1});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})}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);function toRow(m){var row={};self.model.fields.each(function(field){var render="";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);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.onSort,function(e,args){var order=args.sortAsc?"asc":"desc";var sort=[{field:args.sortCol.field,order:order}];self.model.query({sort:sort})});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){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{e.preventDefault()}catch(e){}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){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{self.rendered=false}return this},_setupRowReordering:function(){var self=this;self.grid.setSelectionModel(new Slick.RowSelectionModel);var moveRowsPlugin=new Slick.RowMoveManager({cancelEditOnDrag:true});moveRowsPlugin.onBeforeMoveRows.subscribe(function(e,data){for(var i=0;i<data.rows.length;i++){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;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});for(var i=0;i<rows.length;i++){extractedRows.push(data[rows[i]])}rows.reverse();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)}}data=left.concat(extractedRows.concat(right));var selectedRows=[];for(var i=0;i<rows.length;i++)selectedRows.push(left.length+i);self.model.records.reset(data)});if(this.state.get("gridOptions")&&this.state.get("gridOptions").enableReOrderRow){self.grid.registerPlugin(moveRowsPlugin)}},remove:function(){this._slickHandler.unsubscribeAll();Backbone.View.prototype.remove.apply(this,arguments)},show:function(){if(!this.rendered){if(!this.grid){this.render()}this.grid.init();this.rendered=true}this.visible=true},hide:function(){this.visible=false}});my.GridControl=Backbone.View.extend({className:"recline-row-add",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()},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);(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()}$.extend(true,window,{Slick:{Controls:{ColumnPicker:SlickColumnPicker}}})})(jQuery);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";if(typeof VMM!=="undefined"){VMM.debug=false}my.Timeline=Backbone.View.extend({template:' <div class="recline-timeline"> <div id="vmm-timeline-id"></div> </div> ',startFieldNames:["date","startdate","start","start-date"],endFieldNames:["end","endDate"],elementId:"#vmm-timeline-id",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,nonUSDates:false,timelineJSOptions:{}},options.state);this.state=new recline.Model.ObjectState(stateData);this._setupTemporalField()},render:function(){var tmplData={};var htmls=Mustache.render(this.template,tmplData);this.$el.html(htmls);if($(this.elementId).length>0){this._initTimeline()}},show:function(){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)}},convertRecord:function(record,fields){return this._convertRecord(record,fields)},_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(out.timeline.date.length===0){var tlEntry={startDate:"2000,1,1",headline:"No data to show!"};out.timeline.date.push(tlEntry)}return out},_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},_setupTemporalField:function(){this.state.set({startField:this._checkField(this.startFieldNames),endField:this._checkField(this.endFieldNames)})},_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}})})(jQuery,recline.View);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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);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});this.model.query()}})})(jQuery,recline.View);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="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");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)});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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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());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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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 id="from" name="from" type="text" value="{{from}}" /> &ndash; <label for="to">To</label><input id="to" 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> ',events:{"click .action-pagination-update":"onPaginationUpdate","change input":"onFormSubmit"},initialize:function(){_.bindAll(this,"render");this.listenTo(this.model.queryState,"change",this.render);this.render()},onFormSubmit:function(e){e.preventDefault();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){this.model.queryState.set({from:Math.min(maxRecord,Math.max(formFrom,0))})}else if(this.model.queryState.get("to")!=formTo){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);this.recline=this.recline||{};this.recline.View=this.recline.View||{};(function($,my){"use strict";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 for="q">Search</label> <input class="form-control search-query" type="text" id="q" name="q" value="{{q}}" placeholder="Search data ..."> </div> </div> <button type="submit" class="btn btn-default">Go &raquo;</button> </form> ',events:{"submit form":"onFormSubmit"},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)}})})(jQuery,recline.View);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());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);