var _ = require('underscore'); var cdb = require('internal-carto.js'); var AutoStylerFactory = require('./auto-style/factory'); var TIME_SERIES_TYPE = 'time-series'; var HISTOGRAM_TYPE = 'histogram'; /** * Default widget model * * Note: Currently all widgets have a dependency on a dataview, why it makes sense to have it here. * If you need a widget model that's backed up by a dataview model please implement your own model and adhere to the * public interface instead of extending/hacking this one. */ module.exports = cdb.core.Model.extend({ defaults: { attrsNames: [], show_stats: false, show_source: false }, defaultState: { 'collapsed': false }, initialize: function (attrs, models, opts) { opts = opts || {}; if (!models.dataviewModel) throw new Error('dataviewModel is required.'); if (!models.layerModel) throw new Error('layerModel is required.'); this.dataviewModel = models.dataviewModel; this.layerModel = models.layerModel; // Autostyle could be disabled initially if the styles have an aggregation // If no option, autoStyleEnabled by default this._autoStyleEnabledWhenCreated = opts.autoStyleEnabled === undefined ? true : opts.autoStyleEnabled; this.activeAutoStyler(); this.listenTo(this, 'change:style', this.activeAutoStyler); }, activeAutoStyler: function () { if (this.isAutoStyleEnabled() && !this.autoStyler) { this.autoStyler = AutoStylerFactory.get(this.dataviewModel, this.layerModel, this.get('style')); } }, /** * @public * @param {Object} attrs, not that it should be * @return {Boolean} true if at least one attribute was changed * @throws {Error} Should throw an error if the attrs are invalid or inconsistent */ update: function (attrs) { var wAttrs = _.pick(attrs, this.get('attrsNames')); this.set(wAttrs); this.dataviewModel.update(attrs); this._triggerChangesInAutoStyle(); return !!(this.changedAttributes() || this.dataviewModel.changedAttributes()); }, _triggerChangesInAutoStyle: function () { var changed = this.changed && this.changed.style && this.changed.style.auto_style && this.changed.style.auto_style.definition; var previous = this.previousAttributes(); var former = previous.style && previous.style.auto_style && previous.style.auto_style.definition; if (!_.isEqual(changed, former)) { this.trigger('customAutoStyle', this); } }, /** * @public */ remove: function () { this.dataviewModel.remove(); this.trigger('destroy', this); this.stopListening(); }, isAutoStyleEnabled: function () { var styles = this.get('style'); if (this.get('type') === 'category' || this.get('type') === 'histogram') { if (!styles || !styles.auto_style) { // Only when styles are undefined we check the autostyle option return this._autoStyleEnabledWhenCreated; } return styles && styles.auto_style && styles.auto_style.allowed; } else { return false; } }, getWidgetColor: function () { var styles = this.get('style'); var widgetStyle = styles && styles.widget_style; var widgetColor = widgetStyle && widgetStyle.definition && widgetStyle.definition.color && widgetStyle.definition.color.fixed; var widgetColorChanged = (widgetStyle && widgetStyle.widget_color_changed) || (widgetStyle && !widgetStyle.widget_color_changed && widgetColor !== '#9DE0AD'); return widgetColorChanged && widgetColor; }, hasColorsAutoStyle: function () { var autoStyle = this.getAutoStyle(); var hasDefinedColors = false; if (!autoStyle || _.isEmpty(autoStyle) || _.isEmpty(autoStyle.definition)) { return false; } // Check colors in all geometries _.each(autoStyle.definition, function (geometryStyle) { if (geometryStyle.color && geometryStyle.color.range && geometryStyle.color.range.length > 0) { hasDefinedColors = true; } }, this); return hasDefinedColors; }, getColor: function (name) { if (this.isAutoStyleEnabled() && this.isAutoStyle() && this.get('type') === 'category') { return this.autoStyler.colors.getColorByCategory(name); } else { return this.getWidgetColor(); } }, isAutoStyle: function () { return this.get('autoStyle'); }, autoStyle: function () { if (!this.isAutoStyleEnabled()) return; if (!this.dataForAutoStyle()) return; var layer = this.layerModel; var initialStyle = layer.get('cartocss'); if (!initialStyle && layer.get('meta')) { initialStyle = layer.get('meta').cartocss; } layer.set('initialStyle', initialStyle); var style = this.autoStyler.getStyle(); layer.set('cartocss', style); this.set('autoStyle', true); }, dataForAutoStyle: function () { return this.dataviewModel.get('data').length > 0; }, reapplyAutoStyle: function () { var style = this.autoStyler.getStyle(); this.layerModel.set('cartocss', style); this.set('autoStyle', true); }, cancelAutoStyle: function (noRestore) { if (!noRestore) { this.layerModel.restoreCartoCSS(); } this.set('autoStyle', false); }, getAutoStyle: function () { var style = this.get('style'); var layerModel = this.layerModel; var cartocss = layerModel.get('cartocss') || (layerModel.get('meta') && layerModel.get('meta').cartocss); if (this.isAutoStyleEnabled() && this.autoStyler) { if (style && style.auto_style && style.auto_style.definition) { var toRet = _.extend(style.auto_style, {cartocss: cartocss}); return _.extend({}, toRet, {definition: this.autoStyler.getDef(cartocss)}); } else { return { definition: this.autoStyler.getDef(cartocss), cartocss: cartocss }; } } return {}; }, _updateAutoStyle: function (_model, style) { if (this.autoStyler) { this.autoStyler.updateStyle(style); } if (this.isAutoStyle()) { this.reapplyAutoStyle(); } }, setInitialState: function (state) { this.initialState = state || {}; }, applyInitialState: function () { var attrs = _.extend( this.initialState, {hasInitialState: true} ); this.setState(attrs); }, setState: function (state) { this.set(state); }, getState: function () { var state = {}; for (var key in this.defaultState) { var attribute = this.get(key); var defaultValue = this.defaultState[key]; if (typeof defaultValue !== 'undefined' && typeof attribute !== 'undefined' && !_.isEqual(attribute, defaultValue)) { state[key] = attribute; } } return state; }, forceResize: function () { var type = this.get('type'); if (type === TIME_SERIES_TYPE || type === HISTOGRAM_TYPE) { this.trigger('forceResize'); } } });