// # Recline Backbone Models this.recline = this.recline || {}; this.recline.Model = this.recline.Model || {}; (function($, my) { // ## Dataset my.Dataset = Backbone.Model.extend({ __type__: 'Dataset', // ### initialize initialize: function() { _.bindAll(this, 'query'); this.backend = null; if (this.get('backend')) { this.backend = this._backendFromString(this.get('backend')); } else { // try to guess backend ... if (this.get('records')) { this.backend = recline.Backend.Memory; } } this.fields = new my.FieldList(); this.currentRecords = 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', this.query); this.queryState.bind('facet:add', this.query); // store is what we query and save against // store will either be the backend or be a memory store if Backend fetch // tells us to use memory store this._store = this.backend; if (this.backend == recline.Backend.Memory) { this.fetch(); } }, // ### fetch // // Retrieve dataset and (some) records from the backend. fetch: function() { var self = this; var dfd = $.Deferred(); if (this.backend !== recline.Backend.Memory) { this.backend.fetch(this.toJSON()) .done(handleResults) .fail(function(arguments) { dfd.reject(arguments); }); } else { // special case where we have been given data directly handleResults({ records: this.get('records'), fields: this.get('fields'), useMemoryStore: true }); } function handleResults(results) { var out = self._normalizeRecordsAndFields(results.records, results.fields); 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(arguments) { dfd.reject(arguments); }); } return dfd.promise(); }, // ### _normalizeRecordsAndFields // // Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects // // e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] => // fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] _normalizeRecordsAndFields: function(records, fields) { // if no fields get them from records if (!fields && records && records.length > 0) { // records is array then fields is first row of records ... if (records[0] instanceof Array) { fields = records[0]; records = records.slice(1); } else { fields = _.map(_.keys(records[0]), function(key) { return {id: key}; }); } } // fields is an array of strings (i.e. list of field headings/ids) if (fields && fields.length > 0 && typeof fields[0] === 'string') { // Rename duplicate fieldIds as each field name needs to be // unique. var seen = {}; fields = _.map(fields, function(field, index) { // cannot use trim as not supported by IE7 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; } // TODO: decide whether to keep original name as label ... // return { id: fieldId, label: field || fieldId } return { id: fieldId }; }); } // records is provided as arrays so need to zip together with fields // NB: this requires you to have fields to match arrays if (records && records.length > 0 && records[0] instanceof Array) { records = _.map(records, function(doc) { var tmp = {}; _.each(fields, function(field, idx) { tmp[field.id] = doc[idx]; }); return tmp; }); } return { fields: fields, records: records }; }, save: function() { var self = this; // TODO: need to reset the changes ... return this._store.save(this._changes, this.toJSON()); }, transform: function(editFunc) { var self = this; if (!this._store.transform) { alert('Transform is not supported with this backend: ' + this.get('backend')); return; } this.trigger('recline:flash', {message: "Updating all visible docs. This could take a while...", persist: true, loader: true}); this._store.transform(editFunc).done(function() { // reload data as records have changed self.query(); self.trigger('recline:flash', {message: "Records updated successfully"}); }); }, // ### query // // AJAX method with promise API to get records from the backend. // // It will query based on current query state (given by this.queryState) // updated by queryObj (if provided). // // Resulting RecordList are used to reset this.currentRecords and are // also returned. query: function(queryObj) { var self = this; var dfd = $.Deferred(); this.trigger('query:start'); if (queryObj) { this.queryState.set(queryObj, {silent: true}); } var actualQuery = this.queryState.toJSON(); this._store.query(actualQuery, this.toJSON()) .done(function(queryResult) { self._handleQueryResult(queryResult); self.trigger('query:done'); dfd.resolve(self.currentRecords); }) .fail(function(arguments) { self.trigger('query:fail', arguments); dfd.reject(arguments); }); 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.bind('change', function(doc) { self._changes.updates.push(doc.toJSON()); }); _doc.bind('destroy', function(doc) { self._changes.deletes.push(doc.toJSON()); }); return _doc; }); self.currentRecords.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 // // Get a summary for each field in the form of a `Facet`. // // @return null as this is async function. Provides deferred/promise interface. 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 = $.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); // TODO: probably want replace rather than reset (i.e. just replace the facet with this id) self.fields.get(facetId).facets.reset(facet); }); } dfd.resolve(queryResult); }); return dfd.promise(); }, // ### recordSummary // // Get a simple html summary of a Dataset record in form of key/value list recordSummary: function(record) { var html = '