Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
module.exports = {
Mode: {
NESTED: 'nested',
FLOAT: 'float',
DEFAULT: 'nested'
}
};

View File

@@ -0,0 +1,68 @@
module.exports = {
Size: {
MARKER_MIN: 7,
IMAGE_MIN: 20,
DEFAULT: {
min: 1,
max: 45,
step: 0.5
},
DefaultInput100: {
MIN: 0,
MAX: 100,
STEP: 1
},
DEFAULT_RANGE: [ 5, 20 ]
},
Panes: {
FIXED: 'fixed',
BY_VALUE: 'value',
FILE: 'file'
},
Tabs: {
BINS: 'bins',
QUANTIFICATION: 'quantification'
},
Quantification: {
REFERENCE: {
'Jenks': 'jenks',
'Equal Interval': 'equal',
'Heads/Tails': 'headtails',
'Quantile': 'quantiles'
}
},
Settings: {
COLOR: {
quantifications: {
items: ['jenks', 'equal', 'headtails', 'quantiles', 'category'],
defaultIndex: 0
}
},
COLOR_RAMPS: {
quantifications: {
items: ['quantiles', 'jenks', 'equal', 'headtails', 'category'],
defaultIndex: 0
},
bins: {
items: ['2', '3', '4', '5', '6', '7'],
defaultIndex: 3
}
},
NUMBER: {
quantifications: {
items: ['quantiles', 'jenks', 'equal', 'headtails'],
defaultIndex: 0
},
bins: {
items: ['2', '3', '4', '5', '6', '7'],
defaultIndex: 3
}
}
}
};

View File

@@ -0,0 +1,32 @@
module.exports = {
Type: {
SIMPLE: 'simple',
ANIMATION: 'animation',
HEATMAP: 'heatmap',
POLYGON: 'polygon',
REGIONS: 'regions',
HEXABINS: 'hexabins',
SQUARES: 'squares',
NONE: 'none'
},
Blending: {
SIMPLE: [
'none',
'multiply',
'screen',
'overlay',
'darken',
'lighten',
'color-dodge',
'color-burn',
'xor',
'src-over'
],
ANIMATION: [
'lighter',
'multiply',
'source-over',
'xor'
]
}
};

View File

@@ -0,0 +1,62 @@
var Backbone = require('backbone');
var $ = require('jquery');
var ESC_KEY_CODE = 27;
Backbone.Form.editors.Base = Backbone.Form.editors.Base.extend({
applyESCBind: function (callback) {
this._ESCBindCallback = callback;
this._onKeyDown = this._onKeyDown.bind(this);
document.addEventListener('keydown', this._onKeyDown);
},
_onKeyDown: function (ev) {
var anyModalOpen;
if (ev.which === ESC_KEY_CODE) {
anyModalOpen = this._anyModalOpen();
!anyModalOpen && this._ESCBindCallback();
}
},
applyClickOutsideBind: function (callback) {
this._clickBindCallback = callback;
this._onDocumentClick = this._onDocumentClick.bind(this);
this.$el.attr('data-cid', this.cid);
document.addEventListener('click', this._onDocumentClick);
},
_onDocumentClick: function (e) {
var $el = $(e.target);
var anyModalOpen = this._anyModalOpen();
if ($el.closest('[data-cid="' + this.cid + '"]').length === 0 && !anyModalOpen) {
this._clickBindCallback();
}
},
_anyModalOpen: function () {
var modals = this.options.modals;
if (!modals) {
return false;
}
return modals.isOpen();
},
_destroyBinds: function () {
this._removeESCandClickListeners();
},
_removeESCandClickListeners: function () {
if (this._ESCBindCallback) {
document.removeEventListener('keydown', this._onKeyDown);
}
if (this._clickBindCallback) {
document.removeEventListener('click', this._onDocumentClick);
}
},
remove: function () {
this._destroyBinds && this._destroyBinds();
Backbone.View.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,88 @@
var $ = require('jquery');
var Backbone = require('backbone');
var FactoryHints = require('builder/editor/editor-hints/factory-hints');
var CodeMirrorView = require('builder/components/code-mirror/code-mirror-view');
Backbone.Form.editors.CodeEditor = Backbone.Form.editors.TextArea.extend({
render: function () {
this.setValue(this.value);
this._codemirrorModel = new Backbone.Model({
content: this.value,
readonly: false,
lineNumbers: false
});
FactoryHints.init({
tokens: this.options.tokens,
tableName: false,
columnsName: false
});
this._initViews();
this._toggleDisableState();
return this;
},
getValue: function () {
var val = this.$el.val();
val = this.codeMirrorView.getContent();
return (val === '') ? null : val;
},
_initViews: function () {
this._destroyEditor();
var hints = FactoryHints.reset().hints;
this.codeMirrorView = new CodeMirrorView({
model: this._codemirrorModel,
hints: hints,
mode: 'text/mustache',
autocompleteChars: 2,
autocompletePrefix: '{{',
autocompleteSuffix: '}}',
placeholder: this.options.placeholder
});
this.codeMirrorView.bind('codeChanged', function () {
this.trigger('change', this.codeMirrorView.getContent());
}, this);
var $codeMirrorEl = $(this.codeMirrorView.render().el);
this.$el.replaceWith($codeMirrorEl);
this.setElement($codeMirrorEl);
this.$el.addClass('CodeMirror-formInput');
// The default el is replace it with another dom node
// we should add tracking class manually again
this._addTrackingClass();
},
_addTrackingClass: function () {
if (this.options.trackingClass) {
var trackClasses = this.options.trackingClass + ' track-' + this.options.key + this.options.editorType;
this.$el.addClass(trackClasses);
}
},
_hasEditor: function () {
return this.options.editor && !!this.codeMirrorView;
},
_destroyEditor: function () {
if (this._hasEditor()) {
this.codeMirrorView.remove();
}
},
remove: function () {
this._destroyEditor();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,183 @@
var Backbone = require('backbone');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
var MeasurementsView = require('./measurements-list-view');
var FiltersView = require('./filters-list-view');
var REQUIRED_OPTS = [
'filtersCollection',
'measurementsCollection',
'measurementModel',
'region'
];
var braces = function (value) {
var template = _.template("'{<%- value %>}'");
return template({
value: value
});
};
module.exports = CoreView.extend({
className: 'CDB-Box-modal CustomList',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
// For internal state
this.model = new Backbone.Model({
visible: false
});
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this._generateStackLayoutView();
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:visible', function (mdl, isVisible) {
isVisible ? this.render() : this.clearSubViews();
this._toggleVisibility();
});
},
_generateStackLayoutView: function () {
var createListView = function (stackLayoutModel, opts) {
return this._createListView(stackLayoutModel, opts);
}.bind(this);
var createFilterView = function (stackLayoutModel, opts) {
return this._createFilterView(stackLayoutModel, opts);
}.bind(this);
var stackViewCollection = new Backbone.Collection([{
createStackView: createListView
}, {
createStackView: createFilterView
}]);
this._stackLayoutView = new StackLayoutView({ collection: stackViewCollection });
this.addView(this._stackLayoutView);
this.$el.append(this._stackLayoutView.render().$el);
},
_buildFilters: function () {
var selectedFilters = this._filtersCollection.getSelected();
return _.map(selectedFilters, function (filter) {
return filter.getValue();
}).join(', ');
},
_fetchMeasurements: function () {
var region = this._region;
var fetchOptions = {
region: region && braces(region)
};
return this._measurementsCollection.fetch(fetchOptions);
},
_fetchMeasurementsWithFilter: function () {
var region = this._region;
var filters = this._buildFilters();
var fetchOptions = {
filters: filters && braces(filters),
region: region && braces(region)
};
return this._measurementsCollection.fetch(fetchOptions);
},
_searchMeasurements: function (keyword) {
var region = this._region;
var filters = this._buildFilters();
var fetchOptions = {
filters: filters && braces(filters),
region: region && braces(region),
search: keyword,
abortable: true
};
return this._measurementsCollection.fetch(fetchOptions);
},
_fetchFilters: function () {
var region = this._region;
var fetchOptions = {
region: region && braces(region)
};
return this._filtersCollection.fetch(fetchOptions);
},
_fetchCollections: function () {
var selectedFilters = this._filtersCollection.getSelected();
if (selectedFilters.length > 0) {
this._fetchMeasurementsWithFilter();
} else {
this._fetchMeasurements();
}
},
_createListView: function (stackLayoutModel, opts) {
this._fetchCollections();
var view = new MeasurementsView({
filtersCollection: this._filtersCollection,
measurementsCollection: this._measurementsCollection,
searchMeasurements: this._searchMeasurements.bind(this),
measurementModel: this._measurementModel
});
view.bind('filters', function () {
stackLayoutModel.nextStep();
}, this);
return view;
},
_createFilterView: function (stackLayoutModel, opts) {
// FIXME
// we could save this request, it only depends on region
if (this._filtersCollection.size() === 0) {
this._fetchFilters();
}
var view = new FiltersView({
filtersCollection: this._filtersCollection
});
view.bind('back', function () {
this._measurementsCollection.trigger('maybeFiltersUpdated');
stackLayoutModel.prevStep();
}, this);
return view;
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.isVisible());
}
});

View File

@@ -0,0 +1,12 @@
<% if (typeof isLoading != 'undefined' && isLoading) { %>
<div class="u-flex">
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<%- label %>
<% } %>

View File

@@ -0,0 +1,241 @@
var Backbone = require('backbone');
var _ = require('underscore');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var template = require('./data-observatory-measurements.tpl');
var selectedItemTemplate = require('./data-observatory-measurement-item.tpl');
var DropdownDialogView = require('./data-observatory-dropdown-measurements-view');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var MeasurementsCollection = require('builder/data/data-observatory/measurements-collection');
var FiltersCollection = require('builder/data/data-observatory/filters-collection');
var ENTER_KEY_CODE = 13;
var STATE = {
idle: 'idle',
loading: 'loading',
fetching: 'fetching',
fetched: 'fetched',
error: 'error'
};
var MEASUREMENT_ATTRIBUTES = ['aggregate', 'type', 'label', 'val', 'description', 'filter', 'license'];
Backbone.Form.editors.DataObservatoryDropdown = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'u-ellipsis Editor-formSelect',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
options: {
selectedItemTemplate: selectedItemTemplate
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.template = opts.template || template;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this.measurementModel = this.options.measurementModel;
var fetchOptions = {
configModel: this.options.configModel,
nodeDefModel: this.options.nodeDefModel
};
this.measurementsCollection = new MeasurementsCollection([], fetchOptions);
this.filtersCollection = new FiltersCollection([], fetchOptions);
this._initBinds();
this._dialogView = new DropdownDialogView({
configModel: this.options.configModel,
nodeDefModel: this.options.nodeDefModel,
measurementsCollection: this.measurementsCollection,
filtersCollection: this.filtersCollection,
measurementModel: this.measurementModel,
region: this.options.region
});
},
render: function () {
var isLoading = this._isLoading();
var isDisabled = this.options.disabled;
var item = this.measurementModel;
var placeholder = this._getPlaceholder();
var isNull = this._hasValue();
var label = isNull ? placeholder : item.getName();
var title = item.getName() || '';
this.$el.html(
this.template({
title: title,
label: label,
keyAttr: this.options.keyAttr,
isDisabled: isDisabled,
isLoading: isLoading,
isNull: isNull
})
);
this._popupManager = new PopupManager(this.cid, this.$el, this._dialogView.$el);
this._popupManager.append(this.dialogMode);
if (item) {
this._renderLicense(item);
}
return this;
},
_initBinds: function () {
var hide = function () {
this._dialogView.hide();
this._popupManager && this._popupManager.untrack();
}.bind(this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
this.listenTo(this.measurementsCollection, 'change:selected', this._onItemSelected);
},
_destroyBinds: function () {
this.stopListening(this.measurementsCollection);
Backbone.Form.editors.Base.prototype._destroyBinds.call(this);
},
_getPlaceholder: function (isDisabled) {
var keyAttr = this.options.keyAttr;
var placeholder = this.options.placeholder || _t('components.backbone-forms.select.placeholder', { keyAttr: keyAttr });
return placeholder;
},
_hasValue: function () {
var name = this.measurementModel.getValue();
return name == null || name === '';
},
_isLoading: function () {
var state = this.measurementModel.getState();
return state === STATE.fetching;
},
_onItemSelected: function (mdl) {
var selected = this.measurementsCollection.getSelectedItem();
if (selected) {
this.measurementModel.clear({silent: true});
this.measurementModel.set(_.pick(selected.attributes, MEASUREMENT_ATTRIBUTES));
}
this._dialogView.hide();
this._popupManager.untrack();
this._renderLicense(selected);
this._renderButton(selected).focus();
this.trigger('change', this);
},
_onButtonClick: function () {
this._dialogView.toggle();
this._dialogView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
},
_onButtonKeyDown: function (ev) {
if (ev.which === ENTER_KEY_CODE) {
ev.preventDefault();
if (!this._dialogView.isVisible()) {
ev.stopPropagation();
this._onButtonClick();
} else {
this._popupManager.track();
}
}
},
validate: function () {
var value = this.getValue();
var validators = this.schema.validators;
var getValidator = this.getValidator;
if (!validators) return null;
// Run through validators until an error is found
var error = null;
_.every(validators, function (validator) {
error = getValidator(validator)(value, {});
return !!error;
});
// Return error to be aggregated by list
return error ? error : null; // eslint-disable-line
},
focus: function () {
this.$('.js-button').focus();
},
blur: function () {
this.$('.js-button').blur();
},
getValue: function () {
var item = this.measurementModel;
if (item) {
return item.getValue();
} else if (this.value) {
return this.value;
}
},
setValue: function (value) {
var selectedModel = this.measurementModel;
if (selectedModel) {
this._renderButton(selectedModel);
}
this.value = value;
},
_renderButton: function (mdl) {
var button = this.$('.js-button');
var label = mdl.getName();
var $html = this.options.selectedItemTemplate({
label: label
});
button
.removeClass('is-empty')
.attr('title', label)
.html($html);
return button;
},
_renderLicense: function (mdl) {
var license = mdl.get('license');
var $license = this.$('.js-license');
$license
.removeClass('u-isHidden')
.find('span')
.text(license);
},
remove: function () {
this._popupManager && this._popupManager.destroy();
this._dialogView && this._dialogView.clean();
this._destroyBinds();
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,23 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis
<% if (isDisabled) { %> is-disabled <% } %>
<% if (!label) { %> is-empty <% } %>
<% if (isNull) { %> is-empty <% } %>"
tabindex="0"
title="<%- title %>">
<% if (isLoading) { %>
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark u-iBlock">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
<% } else { %>
<%- label %>
<% } %>
</div>
<div class="js-license CDB-Text CDB-FontSize-small u-altTextColor u-tSpace u-bSpace u-isHidden u-flex">
<a href="https://cartodb.github.io/bigmetadata/licenses.html" target="_blank">
<span class="u-tSpace u-bSpace DataObservatory-license"></span>
</a>
</div>

View File

@@ -0,0 +1,35 @@
var _ = require('underscore');
var CustomListMultiItemView = require('builder/components/custom-list/custom-list-multi-item-view');
var NAME = _.template('<%-name %> (<%- items %>)');
module.exports = CustomListMultiItemView.extend({
render: function () {
this.clearSubViews();
this.$el.empty();
var name = this.model.getName() == null ? 'null' : this.model.getName();
name = name.replace(/"/g, '');
this.$el.append(
this.options.template(
_.extend({
isSelected: this.model.get('selected'),
isDisabled: this.model.get('disabled'),
name: NAME({
name: name,
items: this.model.get('items')
}),
val: this.model.getValue(),
description: this.model.get('description')
})
)
);
this.$el
.attr('data-val', this.model.getValue())
.toggleClass('is-disabled', !!this.model.get('disabled'));
return this;
}
});

View File

@@ -0,0 +1,14 @@
<button type="button" class="CDB-ListDecoration-itemLink u-actionTextColor
<% if (isSelected) { %> is-selected <% } %>
" title="<%= name %>">
<div class="u-flex">
<div class="u-iBlock u-rSpace--m">
<input class="CDB-Checkbox js-input" type="checkbox" name="" value="" <% if (isSelected) { %>checked<% } %> <% if (isDisabled) { %>disabled<% } %> />
<span class="u-iBlock CDB-Checkbox-face"></span>
</div>
<div>
<div class="u-bSpace"><%= name %></div>
<div class="CDB-Size-small u-altTextColor"><%- description %></div>
</div>
</div>
</button>

View File

@@ -0,0 +1,11 @@
<div class="CDB-Box-modalHeader">
<ul class="CDB-Box-modalHeaderItem CDB-Box-modalHeaderItem--block CDB-Box-modalHeaderItem--paddingHorizontal">
<li class="CDB-ListDecoration-item CDB-ListDecoration-itemPadding--vertical CDB-Text CDB-Size-medium u-secondaryTextColor">
<button class="u-actionTextColor js-back u-rSpace">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large"></i>
</button>
<%- headerTitle%>
</li>
</ul>
</div>
<div class="js-content"></div>

View File

@@ -0,0 +1,75 @@
var CoreView = require('backbone/core-view');
var CustomListView = require('builder/components/custom-list/custom-view');
var CustomListItemView = require('./filter-list-item-view');
var itemListTemplate = require('./filter-list-item.tpl');
var template = require('./filter-list-view.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var statusTemplate = require('./list-view-states.tpl');
var REQUIRED_OPTS = [
'filtersCollection'
];
module.exports = CoreView.extend({
events: {
'click .js-back': '_onClickBack'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initBinds();
this._listView = new CustomListView({
className: 'DO-Filters',
typeLabel: _t('components.backbone-forms.data-observatory.dropdown.filter.item-label'),
showSearch: false,
collection: this._filtersCollection,
itemTemplate: itemListTemplate,
itemView: CustomListItemView
});
this.addView(this._listView);
},
render: function () {
this.$el.append(template({
headerTitle: _t('components.backbone-forms.data-observatory.dropdown.filter.header')
}));
this._renderListSection();
return this;
},
_renderListSection: function () {
var status = this._filtersCollection.stateModel.get('state');
if (status === 'fetched') {
this.$('.js-content').html(this._listView.render().$el);
} else {
this._createStatusView(status);
}
},
_createStatusView: function (status) {
var el = statusTemplate({
status: status,
type: _t('components.backbone-forms.data-observatory.dropdown.filter.type')
});
this.$('.js-content').html(el);
},
_onClickBack: function (e) {
this.killEvent(e);
this.trigger('back', this);
},
_initBinds: function () {
this.listenTo(this._filtersCollection.stateModel, 'change:state', this._renderListSection, this);
this.listenTo(this._filtersCollection, 'change:selected', this._onSelectItem, this);
},
_onSelectItem: function (item) {
this.trigger('selectItem', item, this);
}
});

View File

@@ -0,0 +1,13 @@
<% if (status === 'fetching') { %>
<div class="InputColorCategory-loader js-loader">
<div class="CDB-LoaderIcon is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
</div>
<% } else if (status === 'error') { %>
<div class="u-flex u-alignCenter u-justifyCenter CDB-Text CDB-Size-medium u-bSpace--m u-tSpace--m u-errorTextColor"><%- _t('components.backbone-forms.data-observatory.dropdown.error', {
type: type
}) %></div>
<% } %>

View File

@@ -0,0 +1,53 @@
var Backbone = require('backbone');
var _ = require('underscore');
var NestedForm = require('builder/components/form-components/nested-form-custom');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
Backbone.Form.editors.List.MeasurementModel = Backbone.Form.editors.NestedModel.extend({
initialize: function (options) {
Backbone.Form.editors.Base.prototype.initialize.call(this, options);
EditorHelpers.setOptions(this, options);
if (!this.form) throw new Error('Missing required option "form"');
if (!options.schema.model) throw new Error('Missing required "schema.model" option for NestedModel editor');
},
render: function () {
var data = this.value || {};
var NestedModel = this.schema.model;
// Wrap the data in a model if it isn't already a model instance
var modelInstance = (data.constructor === NestedModel) ? data : new NestedModel(data, this.options);
this.nestedForm = new NestedForm({
className: 'Editor-formInner--nested',
model: modelInstance,
idPrefix: this.cid + '_',
fieldTemplate: 'nestedField',
template: _.template('<form data-fields="*"></form>'),
trackingClass: this.options.trackingClass
});
this._observeFormEvents();
this.listenTo(this.nestedForm, 'change', this._onChangeForm, this);
// Render form
this.$el.html(this.nestedForm.render().el);
if (this.hasFocus) {
this.trigger('blur', this);
}
return this;
},
_onChangeForm: function () {
// To translate values and validation to parent form
this.nestedForm.commit();
},
remove: function () {
this.nestedForm && this.nestedForm.remove();
Backbone.Form.editors.NestedModel.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,45 @@
var _ = require('underscore');
var BaseView = require('builder/components/custom-list/custom-list-item-view');
module.exports = BaseView.extend({
render: function () {
this.$el.empty();
this.clearSubViews();
var name = this.model.getName() == null ? 'null' : this.model.getName();
var isSelected = this.model.get('selected');
this.$el.append(
this.options.template(
_.extend({
isSelected: isSelected,
isDisabled: this.model.get('disabled'),
name: name,
val: this.model.getValue(),
description: this.model.get('description')
})
)
);
this.$el
.attr('data-val', this.model.getValue())
.attr('data-selected', isSelected)
.toggleClass('is-disabled', !!this.model.get('disabled'));
return this;
},
_onMouseLeave: function () {
var selected = this.$el.attr('data-selected');
if (!selected) {
this.$el.removeClass('is-highlighted');
}
},
_onMouseEnter: function () {
var selected = this.$el.attr('data-selected');
if (!selected) {
this.$el.addClass('is-highlighted');
}
}
});

View File

@@ -0,0 +1,6 @@
<button type="button" class="CDB-ListDecoration-itemLink u-actionTextColor
<% if (isSelected) { %> is-selected <% } %>
" title="<%- name %>">
<div class="u-bSpace"><%- name %></div>
<div class="CDB-Size-small u-altTextColor DataObservatory-description"><%- description %></div>
</button>

View File

@@ -0,0 +1,38 @@
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var template = require('./measurements-count.tpl');
var REQUIRED_OPTS = [
'model'
];
module.exports = CoreView.extend({
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
var query = this.model.get('query');
var isFiltering = this.model.get('filtering');
var items = this.model.get('items');
var count;
if (query === '' && !isFiltering) {
count = _t('analyses.data-observatory-measure.count.suggested');
} else {
count = _t('analyses.data-observatory-measure.count.search', {
items: items
});
}
this.$el.html(template({
items: count
}));
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:items', this.render);
}
});

View File

@@ -0,0 +1,3 @@
<div class="DataObservatory-count CDB-Text CDB-Size-small Color-ThirdBackground">
<%- items %>
</div>

View File

@@ -0,0 +1,156 @@
var Backbone = require('backbone');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var CustomListView = require('builder/components/custom-list/custom-view');
var CustomListItemView = require('./measurement-list-item-view');
var itemListTemplate = require('./measurement-list-item.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var template = require('./measurements-list.tpl');
var SearchMeasurementView = require('./measurements-search-view');
var CountMeasurementView = require('./measurements-count-view');
var statusTemplate = require('./list-view-states.tpl');
var REQUIRED_OPTS = [
'filtersCollection',
'measurementsCollection',
'searchMeasurements',
'measurementModel'
];
module.exports = CoreView.extend({
options: {
maxItems: 100
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.model = new Backbone.Model({
query: '',
items: this.options.maxItems,
filtered: false
});
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.html(template());
this._createSearchView();
this._createCountView();
this._renderListSection();
return this;
},
_initBinds: function () {
this.listenTo(this._measurementsCollection.stateModel, 'change:state', this._renderListSection);
this.listenTo(this.model, 'change:query', this._search);
},
_createListView: function () {
if (this._listView) {
this._listView.clean();
this.removeView(this._listView);
}
if (!this._isSerching()) {
this._promoteSelected();
}
this.model.set({
items: this._measurementsCollection.size(),
filtering: this._isFiltering()
});
this._listView = new CustomListView({
typeLabel: _t('components.backbone-forms.data-observatory.dropdown.measurement.type'),
showSearch: false,
itemView: CustomListItemView,
collection: this._measurementsCollection,
itemTemplate: itemListTemplate
});
this.addView(this._listView);
this._listView.show();
this.$('.js-list').html(this._listView.$el);
},
_createStatusView: function (status) {
var el = statusTemplate({
status: status,
type: _t('components.backbone-forms.data-observatory.dropdown.measurement.type')
});
this.$('.js-list').html(el);
},
_renderListSection: function () {
var status = this._measurementsCollection.stateModel.get('state');
if (status === 'fetched') {
this._createListView();
} else {
this._createStatusView(status);
}
},
_createSearchView: function () {
this._searchView = new SearchMeasurementView({
model: this.model,
filtersCollection: this._filtersCollection
});
this.addView(this._searchView);
this.$('.js-search').append(this._searchView.render().el);
this.listenTo(this._searchView, 'filters', this._onClickFilters);
},
_onClickFilters: function (e) {
this.killEvent(e);
this.trigger('filters');
},
_createCountView: function () {
var view = new CountMeasurementView({
model: this.model
});
this.addView(view);
this.$('.js-count').append(view.render().el);
},
_search: function () {
var keyword = this.model.get('query');
// This function is passed from the parent
this._searchMeasurements(keyword);
},
_isSerching: function () {
return !!this.model.get('query');
},
_isFiltering: function () {
var selectedFilters = this._filtersCollection.getSelected();
return selectedFilters.length > 0;
},
_promoteSelected: function () {
var selected = this._measurementModel;
if (!selected.getValue()) {
return;
}
var hit = this._measurementsCollection.findWhere({val: selected.getValue()});
if (hit) {
this._measurementsCollection.remove(hit);
}
var promoted = _.clone(selected.attributes);
promoted.selected = true;
this._measurementsCollection.add(promoted, {at: 0});
}
});

View File

@@ -0,0 +1,3 @@
<div class="js-search"></div>
<div class="js-count"></div>
<div class="js-list"></div>

View File

@@ -0,0 +1,67 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var utils = require('builder/helpers/utils');
var template = require('./measurements-search.tpl');
var REQUIRED_OPTS = [
'model',
'filtersCollection'
];
module.exports = CoreView.extend({
className: 'CDB-Box-modalHeader',
events: {
'input .js-input-search': '_search',
'click .js-filters': '_onClickFilters'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
render: function () {
var label = this._getSearchLabel();
this.$el.html(template({
label: label
}));
// Focus the input when rendered
setTimeout(function () {
this._getInput().focus();
}.bind(this), 100);
return this;
},
_getSearchLabel: function () {
var selectedFilters = this._filtersCollection.getSelected().length;
var label = _t('analyses.data-observatory-measure.filters.label');
if (selectedFilters === 1) {
label = _t('analyses.data-observatory-measure.filters.applied.single');
} else if (selectedFilters > 1) {
label = _t('analyses.data-observatory-measure.filters.applied.multiple', {
filters: selectedFilters
});
}
return label;
},
_onClickFilters: function (e) {
this.killEvent(e);
this.trigger('filters');
},
_search: _.debounce(function (e) {
var query = this._getInput().val();
query = query.toLowerCase();
query = utils.sanitizeHtml(query);
this.model.set('query', query);
}, 500),
_getInput: function () {
return this.$('.js-input-search');
}
});

View File

@@ -0,0 +1,11 @@
<div class="CDB-Box-modalHeaderItem">
<div class="u-flex u-grow">
<div class="u-flex u-grow">
<input type="text" name="text" autocomplete="off" placeholder="<%- _t('analyses.data-observatory-measure.search-by-name') %>" class="CDB-InputTextPlain CDB-Text js-input-search">
</div>
<div>
<button class="CDB-Text CDB-Size-medium u-actionTextColor js-filters u-rSpace--m"><%- label %></button>
</div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
var Backbone = require('backbone');
/**
* View model of the fill dialog
*/
module.exports = Backbone.Model.extend({
defaults: {
show: false
},
show: function () {
this.set('show', true);
},
hide: function () {
this.set('show', false);
},
isHidden: function () {
return !this.get('show');
},
/**
* @override {Backbone.Model.prototype.destroy}
*/
destroy: function () {
var args = Array.prototype.slice.call(arguments);
this.trigger.apply(this, ['destroy'].concat(args));
}
});

View File

@@ -0,0 +1,51 @@
var CoreView = require('backbone/core-view');
var DatetimeEditorView = require('./datetime-editor-view');
module.exports = CoreView.extend({
className: 'CDB-Box-modal CustomList CustomList--inputs is-visible has-visibility js-datetimePicker',
initialize: function () {
this._initBinds();
},
render: function () {
this.clearSubViews();
this._datetimeEditorView = new DatetimeEditorView({
model: this.model
});
this.addView(this._datetimeEditorView);
this.$el.append(this._datetimeEditorView.render().el);
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:show', this._onShowChange);
this.listenTo(this.model, 'destroy', this._onDestroy);
},
_onShowChange: function (mdl, show) {
if (show) {
this.$el.show();
this.$el.removeClass('is-closing').addClass('is-opening');
} else {
this.$el.removeClass('is-opening').addClass('is-closing');
this.$el.hide();
}
},
show: function () {
this.model.show();
},
hide: function () {
this.model.hide();
},
_onDestroy: function () {
this.hide();
this.clean();
}
});

View File

@@ -0,0 +1,91 @@
var Backbone = require('backbone');
var Utils = require('builder/helpers/utils');
var moment = require('moment');
var MONTHS = [
{
val: 0,
label: _t('months.january')
}, {
val: 1,
label: _t('months.february')
}, {
val: 2,
label: _t('months.march')
}, {
val: 3,
label: _t('months.april')
}, {
val: 4,
label: _t('months.may')
}, {
val: 5,
label: _t('months.june')
}, {
val: 6,
label: _t('months.july')
}, {
val: 7,
label: _t('months.august')
}, {
val: 8,
label: _t('months.september')
}, {
val: 9,
label: _t('months.october')
}, {
val: 10,
label: _t('months.november')
}, {
val: 11,
label: _t('months.december')
}
];
module.exports = Backbone.Model.extend({
initialize: function () {
this.schema = {
day: {
type: 'Text',
title: '',
validators: [
'required',
/^(([0]?[1-9])|([1-2][0-9])|(3[01]))$/,
function (value, formValues) {
var date = moment(Utils.formatDate(formValues));
if (!date.isValid()) {
return {
type: 'date',
message: _t('components.datepicker.invalid-date')
};
}
}
]
},
month: {
type: 'Select',
title: '',
position: {
'left': 0,
'min-width': '200px'
},
options: MONTHS
},
year: {
title: '',
type: 'Text',
validators: ['required', /^([0-9]{0,4})$/]
},
time: {
title: '',
type: 'Text',
validators: ['required', /^([01]{1}[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/]
}
};
},
getFormattedDate: function () {
return Utils.formatDate(this.toJSON());
}
});

View File

@@ -0,0 +1,65 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var moment = require('moment');
var DatetimeEditorModel = require('./datetime-editor-model');
module.exports = CoreView.extend({
tagName: 'div',
className: 'Table-editorDate',
events: {
'keyup': '_onKeyUp'
},
initialize: function () {
var dateAttr = this.model.get('value');
var date = {};
if (!dateAttr) {
date = moment().utc();
} else {
date = moment(dateAttr).utc();
}
this._formModel = new DatetimeEditorModel({
day: date.date(),
month: date.month(),
year: date.year(),
time: date.format('HH:mm:ss'),
utcOffset: date.utcOffset()
});
this._formModel.bind('change', this._setValue, this);
this.add_related_model(this._formModel);
this._setValue();
},
render: function () {
this._formView = new Backbone.Form({
model: this._formModel
});
this._formView.bind('change', function () {
this.commit();
});
this.$el.html(this._formView.render().el);
return this;
},
_setValue: function () {
this.model.set('value', this._formModel.getFormattedDate());
},
_onKeyUp: function () {
this._setValue();
},
clean: function () {
this._formView.remove();
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,120 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var moment = require('moment');
var DatetimeDialogModel = require('./datetime-dialog-model');
var DatetimeDialogView = require('./datetime-dialog-view');
var template = require('./datetime.tpl');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var PopupManager = require('builder/components/popup-manager');
Backbone.Form.editors.DateTime = Backbone.Form.editors.Base.extend({
className: 'Editor-formInput u-flex u-alignCenter',
events: {
'click .js-input': '_onInputClick'
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this._initBinds();
this.render();
},
render: function () {
Backbone.Form.editors.Base.prototype.render.apply(this, arguments);
this._removeDateTimeDialog();
this.$el.html(
template({
value: this._getFormattedDatetime()
})
);
this._initViews();
return this;
},
_getFormattedDatetime: function () {
if (this.value) {
var value = new Date(this.value).toUTCString();
var momentObj = moment(value).utc();
return momentObj.format('YYYY-MM-DD') + 'T' + momentObj.format('HH:mm:ss') + 'Z';
}
// for datetime type, empty '' value raises an error on Postgres
return null;
},
_initViews: function () {
if (this.options.editorAttrs && this.options.editorAttrs.disabled) {
this.$el.addClass('is-disabled');
}
this._datetimeDialogModel = new DatetimeDialogModel({
value: this._getFormattedDatetime()
});
this._datetimeDialogModel.bind('change:value', this._onInputChanged, this);
this._datetimeDialogView = new DatetimeDialogView({
model: this._datetimeDialogModel
});
this._popupManager = new PopupManager(this.cid, this.$el, this._datetimeDialogView.$el);
},
_initBinds: function () {
var hide = function () {
this._removeDateTimeDialog();
}.bind(this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
},
_onInputClick: function () {
this._datetimeDialogView.render();
this._popupManager.append(this.dialogMode);
this._popupManager.track();
},
_removeDateTimeDialog: function () {
if (this._datetimeDialogView) {
this._popupManager.untrack();
this._datetimeDialogView.remove();
}
},
_onInputChanged: function (mdl) {
var value = this._datetimeDialogModel.get('value');
this.setValue(value);
this.trigger('change', this);
},
_updateInput: function (value) {
this.$('.js-input')
.toggleClass('is-empty', !value)
.text(value);
},
getValue: function () {
return this._datetimeDialogModel.get('value');
},
setValue: function (value) {
this._updateInput(value);
this.value = value;
},
remove: function () {
this._removeDateTimeDialog();
this._popupManager && this._popupManager.destroy();
Backbone.Form.editors.Base.prototype.remove.apply(this);
}
});

View File

@@ -0,0 +1,3 @@
<button type="button" class="CDB-InputText
<% if (!value) { %> is-empty <% } %>
u-txt-left js-input"><%- value ? value : 'null' %></button>

View File

@@ -0,0 +1,175 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var template = require('./enabler-editor.tpl');
/**
* Creates an element that enables another component, tested with:
*
* select, number and input so far.
*/
Backbone.Form.editors.EnablerEditor = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'Editor-checker u-flex u-alignCenter',
events: {
'click .js-check': '_onCheckClicked'
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
// Disable validators unless it's enabled
this.validators = null;
if (!this.options.editor) {
throw new Error('editor options is required');
}
this._editorOptions = this.options.editor;
this.value = this.model.get(opts.key);
this._checkModel = new Backbone.Model({
enabled: !!this.value
});
this.template = template;
this._initBinds();
},
render: function () {
this.$el.html(
this.template({
label: this.options.label,
isChecked: this._isChecked(),
help: this.options.help || '',
isDisabled: !!this.options.isDisabled,
id: this.cid
})
);
if (this.options.help) {
this._helpTooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 's',
offset: 0,
title: function () {
return $(this).data('tooltip');
}
});
}
this._renderComponent();
return this;
},
_initBinds: function () {
this._checkModel.bind('change:enabled', function (mdl, isEnabled) {
this._manageValue(isEnabled);
this._renderComponent();
this._triggerChange();
}, this);
},
_manageValue: function (isEnabled) {
if (isEnabled) {
this.validators = this.options.validators;
this.value = this.options.defaultValue || '';
this._editorComponent.setValue(this.options.defaultValue || '');
} else {
this.validators = null;
this.value = '';
this._editorComponent.setValue('');
}
this.model.set(this.options.keyAttr, this.value);
this.validate();
},
_isChecked: function () {
return this._checkModel.get('enabled');
},
_renderComponent: function () {
if (this._editorComponent) {
this._removeComponent();
}
var isDisabled = !this._isChecked() || this.options.isDisabled;
var EditorClass = Backbone.Form.editors[this._editorOptions.type];
var editorAttrs = _.extend(
this._editorOptions.editorAttrs || {},
{
disabled: isDisabled
}
);
this._editorComponent = new EditorClass(
_.extend(
{
model: this.model,
key: this.options.keyAttr,
editorAttrs: editorAttrs,
trackingClass: this.options.trackingClass,
editorType: this._editorOptions.type
},
_.omit(this._editorOptions, 'editorAttrs', 'type')
)
);
this._editorComponent.bind('change', this._setEditorComponentValue, this);
this.$('.js-editor').html(this._editorComponent.render().el);
// Not all editor implement the focus method
try {
this._editorComponent.focus();
} catch (e) {}
},
_removeComponent: function () {
this._editorComponent.remove();
this._editorComponent.unbind('change', this._setEditorComponentValue, this);
},
_setEditorComponentValue: function () {
this.value = this._editorComponent.getValue();
this._triggerChange();
},
_triggerChange: function () {
this.trigger('change', this);
},
getValue: function () {
if (this._checkModel.get('enabled')) {
return this._editorComponent && this._editorComponent.getValue() || '';
} else {
return '';
}
},
setValue: function (value) {
this._checkModel.set('enabled', !!value);
if (this._editorComponent) {
this._editorComponent.setValue(value);
}
this.value = value;
},
_onCheckClicked: function (ev) {
this._checkModel.set('enabled', $(ev.target).is(':checked'));
},
remove: function () {
if (this._editorComponent) {
this._removeComponent();
}
if (this._helpTooltip) {
this._helpTooltip.clean();
}
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,8 @@
<div class="u-flex Editor-checkerInput CDB-Text CDB-Size-medium u-rSpace--xl">
<input class="CDB-Checkbox js-check" type="checkbox" id="<%- id %>" name="" value="" <% if (isDisabled) { %>disabled<% } %> <% if (isChecked) { %>checked<% } %>>
<span class="CDB-Checkbox-face u-rSpace--m"></span>
<label class="CDB-Text CDB-Size-small u-ellipsis u-upperCase is-semibold u-flex u-alignCenter Editor-formLabel" for="<%- id %>">
<span class="u-ellipsis <% if (help) { %> js-help is-underlined<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %> title="<%- label %>"><%- label %></span>
</label>
</div>
<div class="Editor-checkerComponent js-editor"></div>

View File

@@ -0,0 +1,10 @@
<label class="CDB-Legend u-upperCase CDB-Text is-semibold CDB-Size-small">
<div class="u-flex u-alignCenter">
<div class="u-iBlock u-rSpace--m">
<input class="CDB-Checkbox js-input" type="checkbox" name="" value="" <% if (checked) { %>checked<% } %> <% if (disabled) { %>disabled<% } %>>
<span class="u-iBlock CDB-Checkbox-face"></span>
</div>
<span class="<% if (help) { %> js-help is-underlined<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %> ><%- label %></span>
</div>
</label>
<div class="Editor-checkerComponent js-editor"></div>

View File

@@ -0,0 +1,84 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var $ = require('jquery');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var template = require('./enabler.tpl');
var templateReversed = require('./enabler-reversed.tpl');
Backbone.Form.editors.Enabler = Backbone.Form.editors.Base.extend({
tagName: 'div',
events: {
'change .js-input': '_onCheckChange',
'focus .js-input': function () {
this.trigger('focus', this);
},
'blur .js-input': function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.template = opts.schema && opts.schema.editorAttrs && opts.schema.editorAttrs.reversed ? templateReversed : (opts.template || template);
this._initViews();
},
_initViews: function () {
this.$el.html(
this.template({
checked: this.options.disabled ? false : this.model.get(this.options.key),
label: this.options.label,
id: this.options.inputId,
title: this.options.title,
help: this.options.help || '',
disabled: this.options.disabled
})
);
if (this.options.help) {
this._removeTooltip();
this._helpTooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 's',
title: function () {
return $(this).data('tooltip');
}
});
}
if (this.options.disabled) {
this.undelegateEvents();
}
},
_onCheckChange: function () {
var isEnabled = this.$('.js-input').is(':checked');
this.model.set('enabler', isEnabled);
this.trigger('change', this);
},
getValue: function () {
return this.$('.js-input').is(':checked');
},
setValue: function (value) {
this.$('.js-input')[value ? 'attr' : 'removeAttr']('checked', '');
this.model.set(this.key, value);
},
_removeTooltip: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
},
remove: function () {
this._removeTooltip();
Backbone.Form.editors.Base.prototype.remove.apply(this);
}
});

View File

@@ -0,0 +1,10 @@
<label class="CDB-Legend u-upperCase CDB-Text is-semibold CDB-Size-small">
<div class="u-flex u-alignCenter">
<div class="u-iBlock u-rSpace--m">
<input class="CDB-Checkbox js-input" type="checkbox" name="" value="" <% if (checked) { %>checked<% } %> <% if (disabled) { %>disabled<% } %>>
<span class="u-iBlock CDB-Checkbox-face"></span>
</div>
<span class="<% if (help) { %> js-help is-underlined<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %> ><%- title %></span>
</div>
</label>
<div class="CDB-Text CDB-Size-medium Editor-formInput js-editor"></div>

View File

@@ -0,0 +1,67 @@
var InputFillView = require('builder/components/input-fill/input-fill-view');
var InputColorByValueView = require('builder/components/form-components/editors/fill-color/inputs/input-color-by-value');
var FillConstants = require('builder/components/form-components/_constants/_fill');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'columns',
'query',
'configModel',
'userModel',
'editorAttrs',
'modals',
'dialogMode',
'valueColorInputModel',
'popupConfig'
];
module.exports = InputFillView.extend({
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._initViews();
},
_initInputFields: function () {
InputFillView.prototype._initInputFields.call(this);
this._initColorByValueInput();
},
_initColorByValueInput: function () {
var quantification = this._valueColorInputModel.get('quantification');
if (quantification && FillConstants.Quantification.REFERENCE[quantification]) {
this._valueColorInputModel.set(
'quantification',
FillConstants.Quantification.REFERENCE[quantification], {
silent: true
}
);
}
this._valueColorInputView = new InputColorByValueView({
model: this._valueColorInputModel,
columns: this.options.columns,
hideNumericColumns: this.options.hideNumericColumns,
removeByValueCategory: this.options.removeByValueCategory,
query: this.options.query,
configModel: this.options.configModel,
userModel: this.options.userModel,
modals: this.options.modals,
imageEnabled: this.options.imageEnabled,
editorAttrs: this.options.editorAttrs ? this.options.editorAttrs : {},
disabled: this.options.editorAttrs && this.options.editorAttrs.disabled
});
this._valueColorInputView.bind('click', this._onInputClick, this);
this.$('.js-content').append(this._valueColorInputView.render().$el);
this._inputCollection && this._inputCollection.push(this._valueColorInputModel);
},
afterRender: function () {
this._valueColorInputView && this._valueColorInputView.afterRender();
}
});

View File

@@ -0,0 +1,85 @@
var InputFillView = require('builder/components/input-fill/input-fill-view');
var InputColorFixedView = require('builder/components/form-components/editors/fill-color/inputs/input-color-fixed');
var InputImageView = require('builder/components/form-components/editors/fill-color/inputs/input-image');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'columns',
'editorAttrs',
'dialogMode',
'popupConfig',
'fixedColorInputModel',
'imageInputModel'
];
module.exports = InputFillView.extend({
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
if (this._editorAttrs.imageEnabled) {
this._configModel = this.options.configModel;
this._userModel = this.options.userModel;
this._modals = this.options.modals;
}
this._initViews();
},
_initInputFields: function () {
InputFillView.prototype._initInputFields.call(this);
this._initFillColorInput();
this._initFillImageInput();
},
_initFillColorInput: function () {
this._fillInputColorView = new InputColorFixedView({
model: this._fixedColorInputModel,
columns: this._columns,
configModel: this._configModel,
userModel: this._userModel,
modals: this._modals,
editorAttrs: this._editorAttrs || {},
disabled: this._editorAttrs && this._editorAttrs.disabled
});
this._fillInputColorView.bind('click', this._onInputClick, this);
this.$('.js-content').append(this._fillInputColorView.render().$el);
this._inputCollection && this._inputCollection.push(this._fixedColorInputModel);
},
_initFillImageInput: function () {
if (this._editorAttrs.imageEnabled) {
this._fillInputImageView = new InputImageView({
model: this._imageInputModel,
columns: this._columns,
configModel: this._configModel,
userModel: this._userModel,
modals: this._modals,
query: this._query,
editorAttrs: this._editorAttrs || {},
disabled: this._editorAttrs && this._editorAttrs.disabled
});
this._fillInputImageView.bind('click', this._onInputClick, this);
this.$('.js-content').append(this._fillInputImageView.render().$el);
this._inputCollection && this._inputCollection.push(this._imageInputModel);
}
},
_unbindFormInputs: function () {
this._fillInputColorView.unbind('click', this._onInputClick, this);
if (this._fillInputImageView) {
this._fillInputImageView.unbind('click', this._onInputClick, this);
}
this._inputCollection.unbind('onInputChanged', this._onInputChanged, this);
},
clean: function () {
this._unbindFormInputs();
InputFillView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,259 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var FillColorFixedView = require('builder/components/form-components/editors/fill-color/fill-color-fixed-view');
var FillColorByValueView = require('builder/components/form-components/editors/fill-color/fill-color-by-value-view');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var FillConstants = require('builder/components/form-components/_constants/_fill');
var tabPaneTemplate = require('builder/components/tab-pane/tab-pane.tpl');
var createRadioLabelsTabPane = require('builder/components/tab-pane/create-radio-labels-tab-pane');
Backbone.Form.editors.FillColor = Backbone.Form.editors.Base.extend({
className: 'Form-InputFill Editor-formInput--FillColor',
initialize: function (options) {
Backbone.Form.editors.Base.prototype.initialize.call(this, options);
EditorHelpers.setOptions(this, options);
if (this.options.editorAttrs) {
this.options = _.extend(this.options, {
columns: this.options.options,
query: this.options.query,
configModel: this.options.configModel,
userModel: this.options.userModel,
editorAttrs: this.options.editorAttrs,
modals: this.options.modals
});
this._colorKeyAttribute = options.key;
this._colorAttributes = this.model.get(this._colorKeyAttribute);
this._sizeAttributes = this.model.get('fillSize');
this._imageInputModel = new Backbone.Model(
_.extend({ type: 'image' }, this._colorAttributes)
);
this._fixedColorInputModel = new Backbone.Model(
_.extend({ type: 'color' }, this._colorAttributes)
);
this._valueColorInputModel = new Backbone.Model(
_.extend({ type: 'color' }, this._colorAttributes)
);
}
this._initViews();
},
_initViews: function () {
var self = this;
var geometryName = this.options.editorAttrs.geometryName;
var fixedPane = {
name: FillConstants.Panes.FIXED,
label: _t('form-components.editors.fill.input-number.solid'),
tooltip: _t('editor.style.tooltips.fill.fixed-tab', { type: geometryName }),
tooltipGravity: 's',
createContentView: function () {
return self._generateFixedContentView();
}
};
var valuePane = {
name: FillConstants.Panes.BY_VALUE,
label: _t('form-components.editors.fill.input-number.by-value'),
tooltip: _t('editor.style.tooltips.fill.by-value-tab', { type: geometryName }),
tooltipGravity: 's',
createContentView: function () {
return self._generateValueContentView();
}
};
this._tabPaneTabs = [];
if (this.options.editorAttrs && this.options.editorAttrs.hidePanes) {
var hidePanes = this.options.editorAttrs.hidePanes;
if (!_.contains(hidePanes, FillConstants.Panes.FIXED)) {
this._tabPaneTabs.push(fixedPane);
}
if (!_.contains(hidePanes, FillConstants.Panes.BY_VALUE)) {
this._tabPaneTabs.push(valuePane);
}
} else {
this._tabPaneTabs = [fixedPane, valuePane];
}
var tabPaneOptions = {
tabPaneOptions: {
template: tabPaneTemplate,
tabPaneItemOptions: {
tagName: 'li',
klassName: 'CDB-NavMenu-item'
}
},
tabPaneItemLabelOptions: {
tagName: 'div',
className: 'CDB-Text CDB-Size-medium'
}
};
var selectedTabPaneIndex = this._getSelectedTabPaneIndex();
this._tabPaneTabs[selectedTabPaneIndex].selected = true;
this._tabPaneView = createRadioLabelsTabPane(this._tabPaneTabs, tabPaneOptions);
this.$el.append(this._tabPaneView.render().$el);
},
_getSelectedTabPaneIndex: function () {
var FIXED_TAB_PANE_INDEX = 0;
var VALUE_TAB_PANE_INDEX = 1;
return this._colorAttributes && this._colorAttributes.range &&
this._tabPaneTabs.length > 1
? VALUE_TAB_PANE_INDEX
: FIXED_TAB_PANE_INDEX;
},
_removeDialogs: function () {
this._removeFillColorFixedDialog();
this._removeFillColorByValueDialog();
},
_removeFillColorFixedDialog: function () {
this._fillColorFixedView && this._fillColorFixedView.removeDialog();
},
_removeFillColorByValueDialog: function () {
this._fillColorByValueView && this._fillColorByValueView.removeDialog();
},
_generateFixedContentView: function () {
this._fillColorFixedView = new FillColorFixedView({
model: this.model,
columns: this.options.columns,
configModel: this.options.configModel,
userModel: this.options.userModel,
modals: this.options.modals,
query: this.options.query,
editorAttrs: this.options.editorAttrs,
dialogMode: DialogConstants.Mode.FLOAT,
hideTabs: this.options.hideTabs,
fixedColorInputModel: this._fixedColorInputModel,
imageInputModel: this._imageInputModel,
popupConfig: {
cid: this.cid,
$el: this.$el
}
});
this.applyESCBind(this._removeFillColorFixedDialog);
this.applyClickOutsideBind(this._removeFillColorFixedDialog);
this._fillColorFixedView.on('onInputChanged', function (input) {
this.trigger('change', input);
}, this);
return this._fillColorFixedView;
},
_generateValueContentView: function () {
this._fillColorByValueView = new FillColorByValueView({
columns: this.options.columns,
query: this.options.query,
configModel: this.options.configModel,
userModel: this.options.userModel,
editorAttrs: this.options.editorAttrs,
model: this.model,
dialogMode: DialogConstants.Mode.FLOAT,
categorizeColumns: this.options.categorizeColumns,
hideNumericColumns: this.options.hideNumericColumns,
removeByValueCategory: this.options.removeByValueCategory,
modals: this.options.modals,
hideTabs: this.options.hideTabs,
valueColorInputModel: this._valueColorInputModel,
popupConfig: {
cid: this.cid,
$el: this.$el
}
});
this.applyESCBind(this._removeFillColorByValueDialog);
this.applyClickOutsideBind(this._removeFillColorByValueDialog);
this._fillColorByValueView.on('onInputChanged', function (input) {
this.trigger('change', input);
}, this);
return this._fillColorByValueView;
},
getValue: function (param) {
var selectedTabPaneName = this._tabPaneView.getSelectedTabPaneName();
return selectedTabPaneName === FillConstants.Panes.FIXED
? this._getFillFixedValues()
: this._getFillByValueValues();
},
_getFillFixedValues: function () {
var color = this._fixedColorInputModel.toJSON();
var image = this._imageInputModel.toJSON();
if (color.range) {
color.fixed = color.range[0];
color.opacity = 1;
}
var colorOmitAttributes = [
'createContentView',
'selected',
'type',
'image',
'marker',
'range'
];
var imageOmitAttributes = [
'createContentView',
'selected',
'type',
'fixed',
'range'
];
var colorAttributes = _.omit(color, colorOmitAttributes);
var imageAttributes = _.omit(image, imageOmitAttributes);
var values = _.extend({}, imageAttributes, colorAttributes);
this._fixedColorInputModel.set(values);
this._imageInputModel.set(values);
return values;
},
_getFillByValueValues: function () {
var colorOmitAttributes = [
'createContentView',
'selected',
'type'
];
var values = _.omit(this._valueColorInputModel.toJSON(), colorOmitAttributes);
this._valueColorInputModel.set(values);
return values;
},
remove: function () {
this._removeDialogs();
this._tabPaneView.clean();
this._fillColorByValueView && this._fillColorByValueView.clean();
this._fillColorFixedView && this._fillColorFixedView.clean();
Backbone.Form.editors.Base.prototype.remove.apply(this);
}
});

View File

@@ -0,0 +1,196 @@
var _ = require('underscore');
var $ = require('jquery');
var CoreView = require('backbone/core-view');
var InputColorValueContentView = require('builder/components/input-color/input-color-value-content-view');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var InputColorTemplate = require('builder/components/form-components/editors/fill-color/inputs/input-color-by-value.tpl');
var FillConstants = require('builder/components/form-components/_constants/_fill');
var Utils = require('builder/helpers/utils');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'columns',
'query',
'configModel',
'userModel',
'modals'
];
module.exports = CoreView.extend({
tagName: 'li',
className: 'CDB-OptionInput-item',
events: {
'click': '_onClick'
},
initialize: function (options) {
if (this.options.editorAttrs) {
this._editorAttrs = this.options.editorAttrs;
this._help = this._editorAttrs.help;
this._imageEnabled = this._editorAttrs.imageEnabled;
this._hideNumericColumns = this.options.hideNumericColumns;
this._removeByValueCategory = this.options.removeByValueCategory;
if (this._editorAttrs.hidePanes && !_.contains(this._editorAttrs.hidePanes, FillConstants.Panes.BY_VALUE)) {
if (!options.configModel) throw new Error('configModel param is required');
if (!options.userModel) throw new Error('userModel param is required');
if (!options.modals) throw new Error('modals param is required');
if (!options.query) throw new Error('query param is required');
}
}
if (!options.columns) throw new Error('columns is required');
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.toggleClass('is-disabled', this.options.disabled);
this.$el.html(
InputColorTemplate({
value: this._getValue(),
attribute: this.model.get('attribute'),
opacity: this._getOpacity(),
colorBar: this._getColorbar(),
help: this._help.color || ''
})
);
this._initViews();
return this;
},
afterRender: function () {
this._openPopupForColumn();
},
_openPopupForColumn: function () {
var columnSelected = this.model.get('attribute');
if (_.isUndefined(columnSelected)) {
var self = this;
setTimeout(function () { self.$el.click(); }, 200);
}
},
_getColorbar: function () {
var colors = this._getValue();
if (!_.isArray(colors)) return '';
var deltaPercent = (100.0 / colors.length);
var previousPercent = 0;
var self = this;
var colorsForBar = colors.map(function (color) {
var min = previousPercent;
var max = previousPercent + deltaPercent;
var entry = self._getColorStep(color, [min, max]);
previousPercent = max;
return entry;
});
var colorBar = 'background: linear-gradient(90deg, ' + colorsForBar.join(', ') + ')';
return colorBar;
},
_getColorStep: function (color, minMax) {
return (color + ' ' + minMax[0] + '%, ' + color + ' ' + minMax[1] + '%');
},
_initViews: function () {
if (this._help.color) {
var tooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
},
offset: 8
});
this.addView(tooltip);
}
},
_initBinds: function () {
var self = this;
this.model.set('createContentView', function () {
return self._createContentView();
}).bind(this);
this.model.on('change:selected', this._onToggleSelected, this);
this.model.on('change:opacity change:range change:attribute', this.render, this);
},
_getColumn: function () {
return _.find(this._columns, function (column) {
return column.label === this.model.get('attribute');
}, this);
},
_iconStylingEnabled: function () {
return this._imageEnabled;
},
_createContentView: function () {
this._inputColorValueContentView = new InputColorValueContentView({
model: this.model,
columns: this._columns,
hideNumericColumns: this._hideNumericColumns,
removeByValueCategory: this._removeByValueCategory,
configModel: this._configModel,
categorizeColumns: this._categorizeColumns,
imageEnabled: this._imageEnabled,
userModel: this._userModel,
modals: this._modals,
hideTabs: this._hideTabs,
query: this._query
});
this.model.on('change', this._onChangeValue, this);
return this._inputColorValueContentView;
},
_onClick: function () {
if (this.options.disabled) {
return;
}
this.trigger('click', this.model);
},
_getValue: function () {
return this.model.get('range') && this.model.get('range').length
? this._getRangeColorValues()
: null;
},
_getRangeColorValues: function () {
return _.map(this.model.get('range'), function (color) {
return Utils.hexToRGBA(color, this._getOpacity());
}, this);
},
_getOpacity: function () {
return this.model.get('opacity') != null ? this.model.get('opacity') : 1;
},
_onToggleSelected: function () {
this.$el.toggleClass('is-active', this.model.get('selected'));
},
_onChangeValue: function (color) {
this.model.set({ quantification: color.get('quantification'), range: color.get('range') });
}
});

View File

@@ -0,0 +1,12 @@
<button type="button" class="Editor-fillContainer Editor-fillContainer--ByValue <% if (help) { %>js-help<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %>>
<% if (_.isArray(value)) { %>
<span class="Editor-fillContainer--Column u-ellipsis">
<%- attribute %>
</span>
<span class="Editor-fillContainer--ColorBarContainer ColorBarContainer">
<span class="ColorBar ColorBar-gradient" style="<%- colorBar %>"></span>
</span>
<% } else { %>
<span class="u-altTextColor"><%- _t('form-components.editors.style.select-by-column') %></span>
<% } %>
</button>

View File

@@ -0,0 +1,135 @@
var _ = require('underscore');
var $ = require('jquery');
var CoreView = require('backbone/core-view');
var ColorPickerView = require('builder/components/color-picker/color-picker');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var InputColorTemplate = require('builder/components/form-components/editors/fill-color/inputs/input-color-fixed.tpl');
var Utils = require('builder/helpers/utils');
module.exports = CoreView.extend({
tagName: 'li',
className: 'CDB-OptionInput-item',
events: {
'click': '_onClick'
},
initialize: function (options) {
if (this.options.editorAttrs) {
this._editorAttrs = this.options.editorAttrs;
this._help = this._editorAttrs.help;
}
if (!options.columns) throw new Error('columns is required');
this._columns = options.columns;
this._configModel = options.configModel;
this._userModel = options.userModel;
this._modals = options.modals;
this._query = options.query;
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.toggleClass('is-disabled', this.options.disabled);
this.$el.html(
InputColorTemplate({
value: this._getValue(),
opacity: this._getOpacity(),
help: this._help && this._help.color || null
})
);
this._initViews();
return this;
},
_initViews: function () {
if (this._help && this._help.color) {
var tooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
},
offset: 8
});
this.addView(tooltip);
}
},
_initBinds: function () {
var self = this;
this.model.set('createContentView', function () {
return self._createContentView();
}).bind(this);
this.model.on('change:selected', this._onToggleSelected, this);
this.model.on('change:opacity change:fixed', this.render, this);
},
_getColumn: function () {
return _.find(this._columns, function (column) {
return column.label === this.model.get('attribute');
}, this);
},
_createContentView: function () {
this._colorPickerView = new ColorPickerView({
value: this.model.get('fixed'),
opacity: this.model.get('opacity'),
disableOpacity: this._disableOpacity || false
});
this._initColorPickerViewBindings();
return this._colorPickerView;
},
_initColorPickerViewBindings: function () {
if (this._colorPickerView) {
this._colorPickerView.bind('change', this._onChangeValue, this);
this._colorPickerView.on('onClean', function () {
this._colorPickerView.unbind('change', this._onChangeValue, this);
}, this);
}
},
_onClick: function () {
if (this.options.disabled) {
return;
}
this.trigger('click', this.model);
},
_getValue: function () {
var value = this.model.get('fixed');
return value
? Utils.hexToRGBA(value, this._getOpacity())
: value;
},
_getOpacity: function () {
return this.model.get('opacity') != null ? this.model.get('opacity') : 1;
},
_onToggleSelected: function () {
this.$el.toggleClass('is-active', this.model.get('selected'));
},
_onChangeValue: function (color) {
this.model.set({ fixed: color.hex, opacity: color.opacity });
}
});

View File

@@ -0,0 +1,9 @@
<button type="button" class="Editor-fillContainer u-altTextColor <% if (help) { %>js-help<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %>>
<% if (value) { %>
<ul class="ColorBarContainer">
<li class="ColorBar" style="background-color: <%- value %>"></li>
</ul>
<% } else { %>
<%- _t('form-components.editors.fill.input-color.select-color') %>
<% }%>
</button>

View File

@@ -0,0 +1,168 @@
var _ = require('underscore');
var $ = require('jquery');
var CoreView = require('backbone/core-view');
var InputImageTemplate = require('./input-image.tpl');
var ImageLoaderView = require('builder/components/img-loader-view');
var InputColorFileView = require('builder/components/input-color/input-color-file-view');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var Utils = require('builder/helpers/utils');
module.exports = CoreView.extend({
tagName: 'li',
className: 'CDB-OptionInput-item',
events: {
'click': '_onClick'
},
initialize: function (options) {
if (this.options.editorAttrs) {
this._editorAttrs = this.options.editorAttrs;
this._help = this._editorAttrs.help;
}
this._imageEnabled = true;
this._columns = options.columns;
this._configModel = options.configModel;
this._userModel = options.userModel;
this._modals = options.modals;
this._query = options.query;
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.html(InputImageTemplate({
help: this._help.image || '',
image: this.model.get('image'),
isCustomMarker: this.model.get('kind') === 'custom-marker'
}));
this._initViews();
return this;
},
_initViews: function () {
this.iconView = new ImageLoaderView({
imageClass: 'Editor-fillImageAsset',
imageUrl: this._getImageURL(),
color: this._getColor()
});
this.addView(this.iconView);
this.$('.js-image-container').append(this.iconView.render().el);
if (this._help.image) {
var tooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
},
offset: 8
});
this.addView(tooltip);
}
},
_initBinds: function () {
var self = this;
this.model.set('createContentView', function () {
return self._createContentView();
}).bind(this);
this.model.on('change:images change:image', this.render, this);
this.model.on('change:opacity change:fixed', this._updateImageColor, this);
},
_categoryImagesPresent: function () {
var images = this.model.get('images');
images.forEach(function (image) {
if (!_.isEmpty(image)) return true;
});
return false;
},
_updateImageColor: function () {
if (this.iconView) {
this.iconView.updateImageColor(this._getColor());
}
},
_getColor: function () {
var color = this.model.get('fixed');
return color
? Utils.hexToRGBA(color, this._getOpacity())
: color;
},
_getOpacity: function () {
return this.model.get('opacity') != null ? this.model.get('opacity') : 1;
},
_onClick: function () {
if (this.options.disabled) {
return;
}
this.trigger('click', this.model);
},
_onChangeFile: function (marker) {
var kind, image;
if (marker) {
kind = marker.kind;
image = marker.url;
} else {
kind = null;
image = null;
}
this.model.set({ kind: kind, image: image });
},
_createContentView: function () {
this._inputColorFileView = new InputColorFileView({
model: this.model,
userModel: this._userModel,
configModel: this._configModel,
modals: this._modals
});
this._initInputColorFileViewBindings();
return this._inputColorFileView;
},
_initInputColorFileViewBindings: function () {
if (this._inputColorFileView) {
this._inputColorFileView.bind('change', this._onChangeFile, this);
this._inputColorFileView.on('onClean', function () {
this._inputColorFileView.unbind('change', this._onChangeFile, this);
}, this);
}
},
_getImageURL: function () {
return this.model.get('image') && this._isIconStylingEnabled() ? this.model.get('image') : '';
},
_getKind: function () {
return this.model.get('kind') && this._isIconStylingEnabled() ? this.model.get('kind') : '';
},
_isIconStylingEnabled: function () {
return this._imageEnabled;
}
});

View File

@@ -0,0 +1,11 @@
<% if (image && !isCustomMarker) { %>
<button type="button" class="Editor-fillImage <% if (help) { %> js-help<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %>>
<div class="IconContainer js-image-container"></div>
</button>
<% } %>
<% if (!image || isCustomMarker) { %>
<button type="button" class="Editor-fillImage <% if (help) { %> js-help<% } %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %>>
<div class='CDB-Text CDB-FontSize-small u-altTextColor is-semibold u-upperCase'><%= _t('form-components.editors.fill.input-color.img') %></div>
</button>
<% } %>

View File

@@ -0,0 +1,6 @@
<div class="Editor-boxModalHeader">
<nav class="CDB-NavMenu">
<ul class="CDB-NavMenu-Inner CDB-NavMenu-inner--no-margin CDB-NavMenu-inner--is-dropdown CDB-Text is-semibold CDB-Size-medium js-menu"></ul>
</nav>
</div>
<div class="js-content"></div>

View File

@@ -0,0 +1,16 @@
var CoreView = require('backbone/core-view');
var template = require('./fill-tab.tpl');
module.exports = CoreView.extend({
initialize: function (opts) {
if (!opts.stackLayoutModel) throw new Error('stackLayoutModel is required');
this._stackLayoutModel = opts.stackLayoutModel;
},
render: function () {
this.$el.html(template());
return this;
}
});

View File

@@ -0,0 +1,223 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var DialogModel = require('builder/components/dialog/dialog-model');
var DialogView = require('builder/components/dialog/dialog-view');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var InputCollection = require('./input-collection');
var InputNumber = require('builder/components/input-number/input-number');
var InputColor = require('builder/components/input-color/input-color');
var template = require('builder/components/input-fill/input-fill.tpl');
var PopupManager = require('builder/components/popup-manager');
var INPUT_TYPE_MAP = {
'size': InputNumber,
'color': InputColor,
'image': InputColor
};
var QUANTIFICATION_REF = {
'Jenks': 'jenks',
'Equal Interval': 'equal',
'Heads/Tails': 'headtails',
'Quantile': 'quantiles'
};
var MIN_IMAGE_SIZE = 20;
var MIN_MARKER_SIZE = 7;
Backbone.Form.editors.Fill = Backbone.Form.editors.Base.extend({
className: 'Form-InputFill CDB-OptionInput CDB-Text js-input',
events: {
focus: function () {
this.trigger('focus', this);
},
blur: function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts); // Options
this.options = _.extend(
this.options,
{
columns: this.options.options,
query: this.options.query,
configModel: this.options.configModel,
userModel: this.options.userModel,
editorAttrs: this.options.editorAttrs,
modals: this.options.modals
}
);
this._keyAttr = opts.key;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this._initBinds();
this._initViews();
},
_initViews: function () {
this.$el.append(template());
if (this.options.editorAttrs && this.options.editorAttrs.disabled) {
this.$el.addClass('is-disabled');
}
this._initFillDialog();
this._initInputFields();
this._popupManager = new PopupManager(this.cid, this.$el, this._dialogView.$el);
},
_initInputFields: function () {
var inputKlass;
this._inputCollection = new InputCollection();
_.each(this.model.get(this._keyAttr), function (value, key) {
this._inputCollection.add(_.extend({ type: key }, value));
}, this);
this._inputCollection.each(function (inputModel) {
var type = inputModel.get('type');
var Klass = INPUT_TYPE_MAP[type];
if (!Klass) {
throw new Error(type + ' is not a valid type of constructor');
}
if (inputModel.get('quantification') && QUANTIFICATION_REF[inputModel.get('quantification')]) {
inputModel.set('quantification', QUANTIFICATION_REF[inputModel.get('quantification')], { silent: true });
}
inputKlass = new Klass(({
model: inputModel,
columns: this.options.columns,
query: this.options.query,
configModel: this.options.configModel,
userModel: this.options.userModel,
modals: this.options.modals,
editorAttrs: this.options.editorAttrs ? this.options.editorAttrs[type] : {},
disabled: this.options.editorAttrs && this.options.editorAttrs.disabled
}));
inputKlass.bind('click', this._onInputClick, this);
this.$('.js-content').append(inputKlass.render().$el);
}, this);
this._inputCollection.bind('inputChanged', this._onInputChanged, this);
},
_initBinds: function () {
this.applyESCBind(function () {
this._removeDialog();
});
this.applyClickOutsideBind(function () {
this._removeDialog();
});
},
_onInputClick: function (inputModel) {
if (inputModel.get('selected')) {
this._removeDialog();
return;
}
inputModel.set('selected', true);
this._dialogView.model.set('createContentView', inputModel.get('createContentView'));
this._dialogView.render();
this._dialogView.show();
this._popupManager.append(this.dialogMode);
this._popupManager.track();
},
_onInputChanged: function (model) {
this._adjustImageSize(model);
this.trigger('change', this);
},
_adjustImageSize: function (model) {
if (model.get('type') !== 'color' && model.get('kind') !== 'marker') {
return;
}
var size = this._inputCollection.findWhere({ type: 'size' });
if (!model.get('image') && model.hasChanged('image') && model.previous('image')) {
size.set('fixed', MIN_MARKER_SIZE);
return;
}
if (!model.get('images') && model.hasChanged('images')) {
size.set('fixed', MIN_MARKER_SIZE);
return;
}
var changedImage = model.get('image') && model.hasChanged('image') && !model.previous('image');
var hasImages = _.isEmpty(_.compact(model.get('images')));
var hadImages = _.isEmpty(_.compact(model.previous('images')));
var changedImages = !hasImages && model.hasChanged('images') && hadImages;
if (changedImage || changedImages) {
if (size && size.get('fixed') < MIN_IMAGE_SIZE) {
size.set('fixed', MIN_IMAGE_SIZE);
}
}
},
_initFillDialog: function () {
var dialogModel = new DialogModel();
this.listenToOnce(dialogModel, 'destroy', function () {
this._dialogView = null;
this.stopListening(dialogModel);
});
this._dialogView = new DialogView({
model: dialogModel
});
},
_removeDialog: function (dialog) {
this._inputCollection.unselect();
this._dialogView.clean();
this._popupManager.untrack();
},
focus: function () {
if (this.hasFocus) return;
this.$('.js-fillInput').focus();
},
blur: function () {
if (!this.hasFocus) return;
this.$('.js-fillInput').blur();
},
getValue: function () {
return this._inputCollection.getValues();
},
setValue: function (value) {
// TODO: add setter
},
remove: function () {
this._removeDialog();
this._popupManager.destroy();
this._inputCollection.unbind('inputChanged', this._onInputChanged, this);
Backbone.Form.editors.Base.prototype.remove.apply(this);
}
});

View File

@@ -0,0 +1,82 @@
var Backbone = require('backbone');
var _ = require('underscore');
var INPUT_TYPE_ORDER = ['size', 'color'];
module.exports = Backbone.Collection.extend({
constructor: function (models, options) {
options = _.extend(options || {}, { silent: false });
Backbone.Collection.prototype.constructor.call(this, models, options);
},
comparator: function (m) {
return INPUT_TYPE_ORDER.indexOf(m.get('type'));
},
initialize: function () {
this.bind('change:selected', this._onSelectedChange, this);
this.bind('change', this._onModelsChanged, this);
},
getSelected: function () {
return this.find(function (model) {
return model.get('selected');
});
},
unselect: function () {
this.each(function (model) {
model.set('selected', false);
}, this);
},
_onSelectedChange: function (itemModel, isSelected) {
if (!isSelected) {
return;
}
this.each(function (model) {
if (model !== itemModel) {
model.set('selected', false);
}
}, this);
},
_onModelsChanged: function (mdl) {
var mdlChanges = mdl.changed;
// If there is any change about selected, don't propagate it
if (_.isEmpty(mdlChanges) || (_.size(mdlChanges) === 1 && mdl.changed.hasOwnProperty('selected'))) {
return;
}
this.trigger('inputChanged', mdl, this);
},
getValues: function () {
return this.reduce(function (memo, mdl) {
var data = {};
var typeValues = mdl.toJSON();
if (typeValues.fixed) {
data.fixed = typeValues.fixed;
if (typeValues.type === 'color') {
if (typeValues.image) {
data.image = typeValues.image;
}
if (typeValues.kind) {
data.kind = typeValues.kind;
}
data.opacity = typeValues.opacity;
}
} else {
data = _.omit(typeValues, ['createContentView', 'selected', 'type']);
}
memo[typeValues.type] = data;
return memo;
}, {});
}
});

View File

@@ -0,0 +1,17 @@
<% if (status === 'fetching') { %>
<div class="InputColorCategory-loader js-loader">
<div class="CDB-LoaderIcon is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
</div>
<% } else if (status === 'error') { %>
<div class="u-flex u-alignCenter u-justifyCenter CDB-Text CDB-Size-medium u-bSpace--m u-tSpace--m u-errorTextColor"><%- _t('components.backbone-forms.data-observatory.dropdown.error', {
type: type
}) %></div>
<% } else if (status === 'empty') { %>
<div class="u-flex u-alignCenter u-justifyCenter CDB-Text CDB-Size-medium u-bSpace--m u-tSpace--m"><%- _t('components.backbone-forms.lazy-select.empty', {
type: type
}) %></div>
<% } %>

View File

@@ -0,0 +1,124 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var CustomListView = require('builder/components/custom-list/custom-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var template = require('./lazy-list.tpl');
var SearchView = require('./lazy-search-view');
var statusTemplate = require('./lazy-list-view-states.tpl');
var REQUIRED_OPTS = [
'searchCollection',
'lazySearch'
];
module.exports = CoreView.extend({
className: 'CDB-Box-modal CustomList',
module: 'components:lazy-load:lazy-list-view',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._type = opts.type || _t('components.backbone-forms.lazy-select.type');
this.model = new Backbone.Model({
query: '',
visible: false
});
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.html(template());
this._createSearchView();
this._renderListSection();
return this;
},
_initBinds: function () {
this.listenTo(this._searchCollection.stateModel, 'change:state', this._renderListSection);
this.listenTo(this.model, 'change:query', this._search);
this.listenTo(this.model, 'change:visible', function (mdl, isVisible) {
isVisible ? this.render() : this.clearSubViews();
this._toggleVisibility();
});
},
_createListView: function () {
if (this._listView) {
this._listView.clean();
this.removeView(this._listView);
}
this._listView = new CustomListView({
className: '',
typeLabel: this._type,
showSearch: false,
collection: this._searchCollection,
searchPlaceholder: this.options.searchPlaceholder
});
this.addView(this._listView);
this._listView.show();
this.$('.js-list').html(this._listView.$el);
},
_createStatusView: function (status) {
var el = statusTemplate({
status: status,
type: this._type
});
this.$('.js-list').html(el);
},
_renderListSection: function () {
var status = this._searchCollection.stateModel.get('state');
if (status === 'fetched') {
this._createListView();
} else {
this._createStatusView(status);
}
},
_createSearchView: function () {
this._searchView = new SearchView({
model: this.model
});
this.addView(this._searchView);
this.$('.js-search').append(this._searchView.render().el);
},
_search: function () {
var keyword = this.model.get('query');
// This function is passed from the parent
this._lazySearch(keyword);
},
_isSerching: function () {
return !!this.model.get('query');
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.isVisible());
}
});

View File

@@ -0,0 +1,2 @@
<div class="js-search"></div>
<div class="js-list"></div>

View File

@@ -0,0 +1,115 @@
var Backbone = require('backbone');
var _ = require('underscore');
var $ = require('jquery');
var cdb = require('internal-carto.js');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var BaseModel = require('builder/components/custom-list/custom-list-item-model');
var queryTemplate = _.template("SELECT DISTINCT <%= column %> FROM (<%= sql %>) _table_sql WHERE <%= column %> ilike '%<%= search %>%' ORDER BY <%= column %> ASC");
var REQUIRED_OPTS = [
'configModel',
'rowModel',
'column'
];
module.exports = CustomListCollection.extend({
initialize: function (models, options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._query = options.nodeDefModel.querySchemaModel.get('query');
var configModel = options.configModel;
this.SQL = new cdb.SQL({
user: configModel.get('user_name'),
sql_api_template: configModel.get('sql_api_template'),
api_key: configModel.get('api_key')
});
this.stateModel = new Backbone.Model({
state: _.isEmpty(models) ? 'empty' : 'fetched'
});
CustomListCollection.prototype.initialize.call(this, models, options);
},
model: function (attrs, opts) {
// label and val to custom list compatibility
var key = Object.keys(attrs)[0];
var o = {};
o.val = attrs[key];
o.label = attrs[key];
return new BaseModel(o);
},
_onSelectedChange: function (changedModel, isSelected) {
if (this.type === 'multiple') {
return;
}
if (isSelected) {
this.each(function (m) {
if (m.cid !== changedModel.cid) {
m.set({ selected: false }, { silent: true });
}
}, this);
}
},
buildQuery: function (search) {
var column = this.options.column;
return queryTemplate({
sql: this._query,
search: search,
column: this._rowModel.get(column)
});
},
fetch: function (search) {
this.deferred = $.Deferred();
var sqlQuery = this.buildQuery(search);
this.stateModel.set('state', 'fetching');
this.SQL.execute(sqlQuery, null, {
extra_params: ['page', 'rows_per_page'],
page: 0,
rows_per_page: 40,
success: function (data) {
this._onFetchSuccess(data);
this.stateModel.set('state', 'fetched');
this.deferred.resolve();
}.bind(this),
error: function () {
this.stateModel.set('state', 'error');
this.deferred.reject();
}.bind(this)
});
return this.deferred.promise();
},
_onFetchSuccess: function (data) {
this.reset(data.rows);
if (_.isEmpty(this.models)) {
this.stateModel.set('state', 'empty');
}
},
isFetching: function () {
return this.getState() === 'fetching';
},
getState: function () {
return this.stateModel.get('state');
},
getItem: function (value) {
return this.findWhere({ val: value });
},
isAsync: function () {
return true;
}
});

View File

@@ -0,0 +1,48 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var utils = require('builder/helpers/utils');
var template = require('./lazy-search.tpl');
var REQUIRED_OPTS = [
'model'
];
module.exports = CoreView.extend({
className: 'CDB-Box-modalHeader',
events: {
'input .js-input-search': '_search'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
render: function () {
this.$el.html(template());
// Focus the input when rendered
setTimeout(function () {
this._getInput().focus();
}.bind(this), 100);
return this;
},
_onClickFilters: function (e) {
this.killEvent(e);
this.trigger('filters');
},
_search: _.debounce(function (e) {
var query = this._getInput().val();
query = query.toLowerCase();
query = utils.sanitizeHtml(query);
this.model.set('query', query);
}, 500),
_getInput: function () {
return this.$('.js-input-search');
}
});

View File

@@ -0,0 +1,7 @@
<div class="CDB-Box-modalHeaderItem">
<div class="u-flex u-grow">
<div class="u-flex u-grow">
<input type="text" name="text" autocomplete="off" placeholder="<%- _t('components.backbone-forms.lazy-select.search') %>" class="CDB-InputTextPlain CDB-Text js-input-search">
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
<% if (typeof isLoading != 'undefined' && isLoading) { %>
<div class="u-flex">
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<%- label %>
<% } %>

View File

@@ -0,0 +1,200 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var template = require('./lazy-select.tpl');
var selectedItemTemplate = require('./lazy-select-item.tpl');
var ListView = require('./lazy-list-view');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var SearchCollection = require('./lazy-search-collection');
var ENTER_KEY_CODE = 13;
Backbone.Form.editors.LazySelect = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'u-ellipsis Editor-formSelect',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
options: {
selectedItemTemplate: selectedItemTemplate
},
initialize: function (options) {
Backbone.Form.editors.Base.prototype.initialize.call(this, options);
EditorHelpers.setOptions(this, options);
this.template = options.template || template;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
var fetchOptions = {
configModel: this.options.configModel,
nodeDefModel: this.options.nodeDefModel,
rowModel: this.model,
column: this.options.column
};
if (this.options.options != null) {
this.searchCollection = new SearchCollection(this.options.options, fetchOptions);
} else {
this.searchCollection = this.options.collection;
}
this._initBinds();
var value = this.model.get(this.options.keyAttr);
if (value != null) {
this.setValue(value);
}
var lazySearch = function (search) {
this.searchCollection.fetch(search);
}.bind(this);
var type = this.model.get(this.options.column);
this._listView = new ListView({
configModel: this.options.configModel,
searchCollection: this.searchCollection,
lazySearch: lazySearch,
type: type,
searchPlaceholder: this.options.searchPlaceholder
});
},
render: function () {
var isEmpty = !this.searchCollection.length;
var isDisabled = this.options.disabled;
var name = this.model.get(this.options.keyAttr);
var placeholder = this._getPlaceholder();
var isNull = this._hasValue();
var label = isNull ? placeholder : name;
var title = name || '';
this.$el.html(
this.template({
title: title,
label: label,
keyAttr: this.options.keyAttr,
isEmpty: isEmpty,
isDisabled: isDisabled,
isNull: isNull
})
);
this._popupManager = new PopupManager(this.cid, this.$el, this._listView.$el);
this._popupManager.append(this.dialogMode);
return this;
},
_initBinds: function () {
var hide = function () {
this._listView.hide();
this._popupManager && this._popupManager.untrack();
}.bind(this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
this.listenTo(this.searchCollection, 'change:selected', this._onItemSelected);
},
_destroyBinds: function () {
this.stopListening(this.searchCollection);
Backbone.Form.editors.Base.prototype._destroyBinds.call(this);
},
_getPlaceholder: function (isDisabled) {
var keyAttr = this.options.keyAttr;
var placeholder = this.options.placeholder || _t('components.backbone-forms.select.placeholder', { keyAttr: keyAttr });
return placeholder;
},
_hasValue: function () {
var name = this.model.get(this.options.keyAttr);
return name == null || name === '';
},
_onItemSelected: function (model) {
this._listView.hide();
this._popupManager.untrack();
this._renderButton(model).focus();
this.trigger('change', this);
},
_onButtonClick: function () {
this._listView.toggle();
this._listView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
},
_onButtonKeyDown: function (ev) {
if (ev.which === ENTER_KEY_CODE) {
ev.preventDefault();
if (!this._listView.isVisible()) {
ev.stopPropagation();
this._onButtonClick();
} else {
this._popupManager.track();
}
}
},
focus: function () {
this.$('.js-button').focus();
},
blur: function () {
this.$('.js-button').blur();
},
getValue: function () {
var item = this.searchCollection.getSelectedItem();
if (item) {
return item.getValue();
} else {
return this.value;
}
},
setValue: function (value) {
var selectedModel = this.searchCollection.getSelectedItem();
if (selectedModel) {
this._renderButton(selectedModel);
} else {
this._renderButton(value);
}
this.value = value;
},
_renderButton: function (model) {
var button = this.$('.js-button');
var label = model.getName && model.getName() || model;
var $html = this.options.selectedItemTemplate({
label: label
});
button
.toggleClass('is-empty', label === '')
.attr('title', label)
.html($html);
return button;
},
remove: function () {
this._popupManager && this._popupManager.destroy();
this._listView && this._listView.clean();
this._destroyBinds();
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,12 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis
<% if (isDisabled) { %> is-disabled <% } %>
<% if (!label) { %> is-empty <% } %>
<% if (isNull) { %> is-empty <% } %>"
tabindex="0"
title="<%- title %>">
<% if (isEmpty && isNull) { %>
<%- _t('components.backbone-forms.select.empty') %>
<% } else { %>
<%- label %>
<% } %>
</div>

View File

@@ -0,0 +1,40 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
Backbone.Form.editors.List.CategoryModel = Backbone.Form.editors.NestedModel.extend({
initialize: function (options) {
Backbone.Form.editors.Base.prototype.initialize.call(this, options);
EditorHelpers.setOptions(this, options);
if (!this.form) throw new Error('Missing required option "form"');
if (!options.schema.model) throw new Error('Missing required "schema.model" option for NestedModel editor');
},
render: function () {
// Get the constructor for creating the nested form; i.e. the same constructor as used by the parent form
var NestedForm = this.form.constructor;
var data = this.value || {};
var NestedModel = this.schema.model;
// Wrap the data in a model if it isn't already a model instance
var modelInstance = (data.constructor === NestedModel) ? data : new NestedModel(data, this.options);
this.nestedForm = new NestedForm({
model: modelInstance,
idPrefix: this.cid + '_',
fieldTemplate: 'nestedField'
});
this._observeFormEvents();
// Render form
this.$el.html(this.nestedForm.render().el);
if (this.hasFocus) this.trigger('blur', this);
return this;
}
});

View File

@@ -0,0 +1,169 @@
var Backbone = require('backbone');
var $ = require('jquery');
var _ = require('underscore');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var ENTER_KEY_CODE = 13;
/**
* A single item in the list
*
* @param {editors.List} options.list The List editor instance this item belongs to
* @param {Function} options.Editor Editor constructor function
* @param {String} options.key Model key
* @param {Mixed} options.value Value
* @param {Object} options.schema Field schema
*/
Backbone.Form.editors.List.Item = Backbone.Form.editors.Base.extend({
events: {
'click [data-action="remove"]': function (event) {
event.preventDefault();
this.list.removeItem(this);
},
'keydown input[type=text]': function (event) {
if (event.keyCode !== ENTER_KEY_CODE) return;
event.preventDefault();
this.list.addItem(null, true);
this.list.$list.find('input:last').focus();
}
},
initialize: function (options) {
EditorHelpers.setOptions(this, options);
this.list = options.list;
this.schema = options.schema || this.list.schema;
this.Editor = options.Editor || Backbone.Form.editors.Text;
this.template = options.template || this.schema.itemTemplate || this.constructor.template;
this.errorClassName = options.errorClassName || this.constructor.errorClassName;
},
render: function () {
// Create editor
this.editor = new this.Editor({
key: this.options.key,
schema: this.schema,
value: this.options.value,
list: this.options.list,
item: this,
form: this.options.form,
trackingClass: this.options.trackingClass
}, {
userModel: this.options.userModel,
configModel: this.options.configModel,
modals: this.options.modals
}).render();
// Create main element
var $el = $($.trim(this.template()));
$el.find('[data-editor]').append(this.editor.el);
var $tooltip = $el.find('.js-remove-help');
if ($tooltip.length) {
this._removeTooltip();
this._tooltip = new TipsyTooltipView({
el: $tooltip,
gravity: 'w',
title: function () {
return _t('editor.legend.tooltips.item.remove');
}
});
}
// Replace the entire element so there isn't a wrapper tag
this.setElement($el);
return this;
},
getValue: function () {
return this.editor.getValue();
},
setValue: function (value) {
this.editor.setValue(value);
},
focus: function () {
this.editor.focus();
},
blur: function () {
this.editor.blur();
},
validate: function () {
var value = this.getValue();
var formValues = this.list.form ? this.list.form.getValue() : {};
var validators = this.schema.validators;
var getValidator = this.getValidator;
if (!validators) return null;
// Run through validators until an error is found
var error = null;
_.every(validators, function (validator) {
error = getValidator(validator)(value, formValues);
return !!error;
});
// Show/hide error
if (error) {
this.setError(error);
} else {
this.clearError();
}
// Return error to be aggregated by list
return error ? error : null; // eslint-disable-line
},
/**
* Show a validation error
*/
setError: function (err) {
this.$el.addClass(this.errorClassName);
this.$el.attr('title', err.message);
},
/**
* Hide validation errors
*/
clearError: function () {
this.$el.removeClass(this.errorClassName);
this.$el.attr('title', null);
},
_removeTooltip: function () {
if (this._tooltip) {
this._tooltip.clean();
}
},
remove: function () {
this._removeTooltip();
this.editor.remove();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
}, {
/* eslint-disable */
template: _.template('\
<div>\
<span data-editor></span>\
<button type="button" data-action="remove">&times;</button>\
</div>\
', null, Backbone.Form.templateSettings),
errorClassName: 'error'
/* eslint-enable */
});

View File

@@ -0,0 +1,330 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var OPTIONS = [
'userModel',
'configModel',
'modals',
'layerDefinitionModel',
'trackingClass'
];
Backbone.Form.editors.List = Backbone.Form.editors.Base.extend({
events: {
'click [data-action="add"]': function (event) {
event.preventDefault();
if (this._canAddNewItems()) {
this.addItem(null, true);
}
}
},
initialize: function (options) {
options = options || {};
var editors = Backbone.Form.editors;
editors.Base.prototype.initialize.call(this, options);
EditorHelpers.setOptions(this, options);
var schema = this.schema;
if (!schema) throw new Error("Missing required option 'schema'");
this.template = options.template || schema.listTemplate || this.constructor.template;
// Determine the editor to use
this.Editor = (function () {
var type = schema.itemType;
// Default to Text
if (!type) return editors.Text;
// Use List-specific version if available
if (editors.List[type]) return editors.List[type];
// Or whichever was passed
return editors[type];
})();
_.each(OPTIONS, function (item) {
if (this.options[item]) {
this['_' + item] = this.options[item];
}
}, this);
this.items = [];
this.errors = [];
this._initBinds();
},
render: function () {
var self = this;
var value = this.value || [];
// Create main element
var $el = $($.trim(this.template()));
// Store a reference to the list (item container)
this.$list = $el.is('[data-items]') ? $el : $el.find('[data-items]');
this.setElement($el);
this.$el.attr('id', this.id);
this.$el.attr('name', this.key);
// Add existing items
if (value.length) {
_.each(value, function (itemValue) {
self.addItem(itemValue);
});
} else {
// If no existing items create an empty one, unless the editor specifies otherwise
if (!this.Editor.isAsync) this.addItem(null, true);
}
if (this.hasFocus) this.trigger('blur', this);
this.validate();
return this;
},
_initBinds: function () {
if (this.options.maxItems) {
this.listenTo(this, 'add remove', this._setAddButtonState);
}
},
/**
* Add a new item to the list
* @param {Mixed} [value] Value for the new item editor
* @param {Boolean} [userInitiated] If the item was added by the user clicking 'add' or pressing `Enter`
*/
addItem: function (value, userInitiated) {
var self = this;
var editors = Backbone.Form.editors;
var defaultOptions = {
list: this,
form: this.form,
schema: this.schema,
value: value,
Editor: this.Editor,
key: this.key
};
var customOptions = _.reduce(OPTIONS, function (memo, item) {
if (this.options[item]) {
memo[item] = this['_' + item];
}
return memo;
}, {}, this);
var options = _.extend({}, defaultOptions, customOptions);
// Create the item
var item = new editors.List.Item(options).render();
var _addItem = function () {
self.items.push(item);
self.$list.append(item.el);
item.editor.on('all', function (event) {
if (event === 'change') return;
// args = ["key:change", itemEditor, fieldEditor]
var args = _.toArray(arguments);
args[0] = 'item:' + event;
args.splice(1, 0, self);
// args = ["item:key:change", this=listEditor, itemEditor, fieldEditor]
editors.List.prototype.trigger.apply(this, args);
}, self);
item.editor.on('change', function () {
if (!item.addEventTriggered) {
item.addEventTriggered = true;
this.trigger('add', this, item.editor);
}
this.trigger('item:change', this, item.editor);
this.trigger('change', this);
}, self);
item.editor.on('focus', function () {
if (this.hasFocus) return;
this.trigger('focus', this);
}, self);
item.editor.on('blur', function () {
if (!this.hasFocus) return;
var self = this;
setTimeout(function () {
if (_.find(self.items, function (item) {
return item.editor.hasFocus;
})) return;
self.trigger('blur', self);
}, 0);
}, self);
if (userInitiated || value) {
item.addEventTriggered = true;
}
if (userInitiated) {
self.trigger('add', self, item.editor);
self.trigger('change', self);
}
};
// Check if we need to wait for the item to complete before adding to the list
if (this.Editor.isAsync) {
item.editor.on('readyToAdd', _addItem, this);
} else {
// Most editors can be added automatically
_addItem();
item.editor.focus();
}
return item;
},
/**
* Remove an item from the list
* @param {List.Item} item
*/
removeItem: function (item) {
// Confirm delete
var confirmMsg = this.schema.confirmDelete;
if (confirmMsg && !confirm(confirmMsg)) return; // eslint-disable-line
var index = _.indexOf(this.items, item);
this.items[index].remove();
this.items.splice(index, 1);
if (item.addEventTriggered) {
this.trigger('remove', this, item.editor);
this.trigger('change', this);
}
if (!this.items.length && !this.Editor.isAsync) this.addItem();
},
getValue: function () {
var values = _.map(this.items, function (item) {
return item.getValue();
});
// Filter empty items
return _.without(values, undefined, '');
},
setValue: function (value) {
this.value = value;
this.render();
},
focus: function () {
if (this.hasFocus) return;
if (this.items[0]) this.items[0].editor.focus();
},
blur: function () {
if (!this.hasFocus) return;
var focusedItem = _.find(this.items, function (item) {
return item.editor.hasFocus;
});
if (focusedItem) focusedItem.editor.blur();
},
/**
* Override default remove function in order to remove item views
*/
remove: function () {
_.invoke(this.items, 'remove');
Backbone.Form.editors.Base.prototype.remove.call(this);
},
/**
* Run validation
*
* @return {Object|Null}
*/
validate: function () {
if (!this.validators) return null;
// Collect errors
var errors = _.map(this.items, function (item) {
return item.validate();
});
// Max items Check
if (this.options.maxItems && this.options.maxItems < this.items.length) {
errors.push(_t('form-components.editors.list.max-items'));
}
this.errors = _.compact(errors);
// Check if any item has errors
var hasErrors = !!this.errors.length;
this._setAddButtonState();
if (!hasErrors) return null;
},
_errorPresenter: function (errors) {
return _.unique(this.errors.map(function (error) {
return error.message;
})).join('\n');
},
_setAddButtonState: function () {
var $button = this._getAddButtonElement();
var hasErrors = !!this.errors.length;
$button.toggleClass('is-disabled', hasErrors);
if (hasErrors) {
this._errorTooltip = new TipsyTooltipView({
el: $button,
gravity: 's',
offset: 0,
title: this._errorPresenter.bind(this)
});
} else {
if (this._errorTooltip) {
this._errorTooltip.clean();
this._errorTooltip = null;
}
}
if (this.options.maxItems) {
$button.toggle(this.items.length < this.options.maxItems);
}
},
_getAddButtonElement: function () {
return this.$('[data-action="add"]');
},
_canAddNewItems: function () {
var opts = this.options;
var maxItemsReached = (opts.maxItems && opts.maxItems <= this.items.length) || false;
return !this.errors.length && !maxItemsReached;
}
}, {
/* eslint-disable */
template: _.template('\
<div>\
<div data-items></div>\
<button type="button" data-action="add">Add</button>\
</div>\
', null, Backbone.Form.templateSettings)
/* eslint-enable */
});

View File

@@ -0,0 +1,32 @@
var CustomListItemView = require('builder/components/custom-list/custom-list-item-view');
var _ = require('underscore');
module.exports = CustomListItemView.extend({
render: function () {
this.$el.empty();
this.clearSubViews();
this.$el.append(
this.options.template(
_.extend(
{
typeLabel: this.options.typeLabel,
isSelected: this.model.get('selected'),
isSourceType: this.model.get('isSourceType'),
name: this.model.getName(),
val: this.model.getValue()
},
this.model.attributes
)
)
);
this.$el
.attr('data-val', this.model.getValue())
.toggleClass('is-disabled', !!this.model.get('disabled'));
return this;
}
});

View File

@@ -0,0 +1,25 @@
<button type="button" class="CDB-ListDecoration-itemLink u-ellipsis u-actionTextColor" title="<%- val %>">
<% if (typeof type != 'undefined' && type === 'node') { %>
<div class="u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-bSpace--s u-upperCase" style="color: <%- color %>;">
<%- val %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- color %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-medium u-lSpace">
<%= nodeTitle %>
</span>
<span class="CDB-Text CDB-Size-medium u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
<% } else { %>
<%- val %>
<% } %>
</button>

View File

@@ -0,0 +1,34 @@
<% if (typeof isLoading != 'undefined' && isLoading) { %>
<div class="u-flex">
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<% if (typeof type != 'undefined' && type === 'node') { %>
<div class="u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-bSpace--s u-upperCase" style="color: <%- color %>;">
<%- val %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- color %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-medium u-lSpace">
<%= nodeTitle %>
</span>
<span class="CDB-Text CDB-Size-medium u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
<% } else { %>
<%- label %>
<% } %>
<% } %>

View File

@@ -0,0 +1,14 @@
var Backbone = require('backbone');
var NodeDatasetItemView = require('./node-dataset-item-view');
var nodeDatasetSelectedTemplate = require('./node-dataset-selected.tpl');
var nodeDatasetItemTemplate = require('./node-dataset-item.tpl');
Backbone.Form.editors.NodeDataset = Backbone.Form.editors.Select.extend({
options: {
selectedItemTemplate: nodeDatasetSelectedTemplate,
itemListTemplate: nodeDatasetItemTemplate,
customListItemView: NodeDatasetItemView
}
});

View File

@@ -0,0 +1,218 @@
var Backbone = require('backbone');
var _ = require('underscore');
var $ = require('jquery');
var template = require('./number.tpl');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
require('jquery');
require('jquery-ui');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var UP_ARROW_KEY_CODE = 38;
var BOTTOM_ARROW_KEY_CODE = 40;
var SPACE_KEY_CODE = 32;
var ENTER_KEY_CODE = 13;
var SMALL_INCREMENT = 1;
var BIG_INCREMENT = 10;
Backbone.Form.editors.Number = Backbone.Form.editors.Base.extend({
tagName: 'ul',
className: 'CDB-OptionInput-container CDB-OptionInput-container--noMargin u-grow',
// backbone-forms 0.14.1 has a 'number' validator:
// https://github.com/powmedia/backbone-forms/blob/v0.14.1/src/validators.js#L64
// We're on 0.14.0 so we have to use a 'regexp' validator
options: {
min: 0,
max: 10,
step: 1,
showSlider: true,
validators: [{
type: 'regexp',
regexp: /^[+-]?((\.\d+)|(\d+(\.\d+)?))$/,
message: _t('editor.edit-feature.valid')
}]
},
events: {
'keydown .js-input': '_onInputKeyDown',
'keyup .js-input': '_onInputKeyUp',
focus: function () {
this.trigger('focus', this);
},
blur: function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
// Setting min, max and step from validators, if exists.
this._validators = opts.validators || (opts.schema && opts.schema.validators);
if (this._validators && this._validators[1]) {
this.options.min = this._validators[1].min;
this.options.max = this._validators[1].max;
this.options.step = this._validators[1].step;
}
if (this.options.editorAttrs && this.options.editorAttrs.help) {
this._help = this.options.editorAttrs.help;
}
this._debouncedTriggerChange = _.debounce(this._triggerChange, 333).bind(this);
this._initViews();
},
_initViews: function () {
var placeholder = (this.value === null) ? 'null' : '';
this.$el.html(
template({
value: this.value,
isDisabled: this.options.disabled,
hasSlider: this.options.showSlider,
isFormatted: this.options.isFormatted,
placeholder: placeholder,
help: this._help || ''
})
);
if (this.options.showSlider) {
this.$('.js-slider').slider({
range: 'min',
value: this.value,
min: this.options.min,
max: this.options.max,
step: this.options.step,
orientation: 'horizontal',
disabled: this.options.disabled,
slide: this._onSlideChange.bind(this),
stop: this._onSlideStop.bind(this)
});
}
if (this._help) {
this._removeTooltip();
this._helpTooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
}
});
}
this.$el.toggleClass('CDB-OptionInput-container--noSlider', !!this.options.showSlider);
},
_onSlideChange: function (ev, ui) {
this.value = ui.value;
this.$('.js-input').val(this.value);
},
_onSlideStop: function (ev, ui) {
this._debouncedTriggerChange();
},
_onInputKeyDown: function (event) {
var $input = this.$('.js-input');
var value = +this.getValue();
var increment = event.shiftKey === true ? BIG_INCREMENT : SMALL_INCREMENT;
switch (event.which) {
case SPACE_KEY_CODE:
case ENTER_KEY_CODE:
return false;
case UP_ARROW_KEY_CODE:
value = this._nextValue(value, increment);
$input.val(value);
break;
case BOTTOM_ARROW_KEY_CODE:
value = this._nextValue(value, increment * -1);
$input.val(value);
break;
default:
// Any other case!
}
},
_nextValue: function (value, increment) {
var nextValue = value += increment;
if (nextValue < this.options.min) return this.options.min;
if (nextValue > this.options.max) return this.options.max;
return nextValue;
},
_hasSlider: function () {
return this.options.showSlider && this.$('.js-slider').data('ui-slider');
},
_onInputKeyUp: function () {
var value = this.$('.js-input').val();
if (this._hasSlider()) {
this.$('.js-slider').slider('value', value);
}
this.value = value;
this._debouncedTriggerChange();
},
_triggerChange: function () {
this.trigger('change', this);
},
focus: function () {
if (this.hasFocus) return;
this.$('.js-input').focus();
},
blur: function () {
if (!this.hasFocus) return;
this.$('.js-input').blur();
},
getValue: function () {
var val = this.$('.js-input').val();
return (val === '') ? null : +val;
},
setValue: function (value) {
if (this._hasSlider()) {
this.$('.js-slider').slider('value', value);
}
this.$('.js-input').val(value);
this.value = value;
},
_destroySlider: function () {
if (this._hasSlider()) {
this.$('.js-slider').slider('destroy');
}
},
_removeTooltip: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
},
remove: function () {
this._destroySlider();
this._removeTooltip();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,8 @@
<% if (hasSlider) { %>
<li class="CDB-OptionInput-item CDB-OptionInput-item--noSeparator">
<div class="UISlider js-slider"></div>
</li>
<% } %>
<li class="CDB-OptionInput-item">
<input type="text" class="CDB-InputText <% if (isFormatted) { %>is-number<% } %> <% if (isDisabled) { %>is-disabled<% } %> js-input <% if (help) { %> js-help<% } %>" <% if (isDisabled) { %>readonly<% } %> value="<%- value %>" placeholder="<%- placeholder %>" <% if (help) { %> data-tooltip="<%- help %>"<% } %> />
</li>

View File

@@ -0,0 +1,23 @@
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var _ = require('underscore');
/*
* Custom list collection, it parses pairs like:
*
* [{ val, label }]
* ["string"]
*/
module.exports = CustomListCollection.extend({
search: function (query) {
query = query.toLowerCase();
return _(this.filter(function (model) {
var val = model.getName().toLowerCase();
var type = model.get('type').toLowerCase();
return ~val.indexOf(query) && type === 'number';
}));
}
});

View File

@@ -0,0 +1,3 @@
<p class="CDB-Text CDB-Size-medium u-altTextColor Editor-dropDownInfoText">
<%- _t('components.backbone-forms.operators.count-message') %>
</p>

View File

@@ -0,0 +1,25 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
defaults: {
visible: false,
operator: 'count',
attribute: ''
},
isValidOperator: function () {
var operator = this.get('operator');
var attribute = this.get('attribute');
if (operator === 'count') {
return true;
} else {
if (operator && attribute) {
return true;
}
return false;
}
}
});

View File

@@ -0,0 +1,150 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var $ = require('jquery');
var CustomListView = require('builder/components/custom-list/custom-view');
var DropdownOverlayView = require('builder/components/dropdown-overlay/dropdown-overlay-view');
var OperatorsListModel = require('./operators-list-model');
var emptyTemplate = require('./operators-list-count.tpl');
var template = require('./operators-list.tpl');
module.exports = CoreView.extend({
className: 'Editor-dropdown Editor-boxModal Editor-dropdownOperators',
tagName: 'div',
events: {
'change input[name="operator"]': '_onOperationChange'
},
initialize: function (opts) {
this.model = new OperatorsListModel({
operator: opts.operator,
attribute: opts.attribute,
visible: false
});
if (opts.attribute) {
this.collection.setSelected(opts.attribute);
}
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.append(
template({
operator: this.model.get('operator')
})
);
this._renderList();
this._dropdownOverlay = new DropdownOverlayView({
onClickAction: this.hide.bind(this),
visible: this.model.get('visible')
});
this.addView(this._dropdownOverlay);
return this;
},
_initBinds: function () {
this.model.bind('change:operator change:attribute', _.debounce(this._onModelChange.bind(this), 10));
this.model.bind('change:visible', function (mdl, isVisible) {
this._toggleVisibility();
if (!isVisible) {
this._destroyList();
this.$('.js-list').html(emptyTemplate());
} else {
this.render();
}
}, this);
this.collection.bind('change:selected', this._onAttributeSelected, this);
this.add_related_model(this.collection);
},
_hasList: function () {
return !!this._listView;
},
_renderList: function () {
if (this.model.get('operator') !== 'count') {
if (this._hasList()) {
return;
}
this._listView = new CustomListView({
className: 'CDB-Dropdown-options CDB-Text CDB-Size-medium',
collection: this.collection,
showSearch: true,
typeLabel: this.options.keyAttr
});
this.$('.js-list').html(this._listView.render().el);
} else {
this._destroyList();
this.$('.js-list').html(emptyTemplate());
}
},
_destroyList: function () {
if (this._hasList()) {
this.removeView(this._listView);
this._listView.clean();
delete this._listView;
}
},
_onModelChange: function () {
if (this.model.isValidOperator()) {
this.trigger('change', this.model.toJSON(), this);
}
},
_onOperationChange: function (ev) {
var $input = $(ev.target);
var operator = $input.val();
if (operator === 'count') {
this.model.set('attribute', '');
this.collection.removeSelected();
}
this.model.set('operator', operator);
this._renderList();
},
_onAttributeSelected: function (mdl) {
if (mdl.get('selected')) {
this.model.set('attribute', mdl.getValue());
}
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.model.get('visible'));
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
remove: function () {
this._destroyList();
this.$el.empty();
CoreView.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,28 @@
<ul class="Editor-dropdownCalculations CDB-Text is-semibold">
<li class="Editor-dropdownCalculationsElement CDB-Fieldset">
<input class="CDB-Radio" type="radio" name="operator" value="count" <% if (operator === 'count') { %>checked<% } %>>
<span class="u-iBlock CDB-Radio-face"></span>
<label class="u-iBlock u-lSpace"><%- _t('operators.count') %></label>
</li>
<li class="Editor-dropdownCalculationsElement CDB-Fieldset">
<input class="CDB-Radio" type="radio" name="operator" value="sum" <% if (operator === 'sum') { %>checked<% } %>>
<span class="u-iBlock CDB-Radio-face"></span>
<label class="u-iBlock u-lSpace"><%- _t('operators.sum') %></label>
</li>
<li class="Editor-dropdownCalculationsElement CDB-Fieldset">
<input class="CDB-Radio" type="radio" name="operator" value="avg" <% if (operator === 'avg') { %>checked<% } %>>
<span class="u-iBlock CDB-Radio-face"></span>
<label class="u-iBlock u-lSpace"><%- _t('operators.avg') %></label>
</li>
<li class="Editor-dropdownCalculationsElement CDB-Fieldset">
<input class="CDB-Radio" type="radio" name="operator" value="max" <% if (operator === 'max') { %>checked<% } %>>
<span class="u-iBlock CDB-Radio-face"></span>
<label class="u-iBlock u-lSpace"><%- _t('operators.max') %></label>
</li>
<li class="Editor-dropdownCalculationsElement CDB-Fieldset">
<input class="CDB-Radio" type="radio" name="operator" value="min" <% if (operator === 'min') { %>checked<% } %>>
<span class="u-iBlock CDB-Radio-face"></span>
<label class="u-iBlock u-lSpace"><%- _t('operators.min') %></label>
</li>
</ul>
<div class="js-list"></div>

View File

@@ -0,0 +1,165 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var _ = require('underscore');
var $ = require('jquery');
var OperatorListView = require('./operators-list-view');
var OperatorListCollection = require('./operators-list-collection');
var template = require('./operators.tpl');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var ENTER_KEY_CODE = 13;
Backbone.Form.editors.Operators = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'Editor-formSelect u-ellipsis',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.collection = new OperatorListCollection(this.options.options);
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
if (this.options.editorAttrs && this.options.editorAttrs.help) {
this._help = this.options.editorAttrs.help;
}
this._initViews();
this.setValue(this.model.get(this.options.keyAttr));
this._initBinds();
},
_initViews: function () {
var value = this.model.get(this.options.keyAttr);
this.$el.append(
$('<div>').addClass('js-operator')
);
this._setOperatorTemplate();
this._operatorsListView = new OperatorListView({
operator: value.operator,
attribute: value.attribute,
collection: this.collection
});
this._operatorsListView.bind('change', this._onOperatorsChange, this);
this._popupManager = new PopupManager(this.cid, this.$el, this._operatorsListView.$el);
this._popupManager.append(this.dialogMode);
if (this.options.disabled) {
this.undelegateEvents();
}
},
_setOperatorTemplate: function () {
this.$('.js-operator').html(
template({
name: this._getOperatorsTextValue(),
disabled: this.options.disabled,
keyAttr: this.options.keyAttr,
help: this._help || ''
})
);
if (this._help) {
this._removeTooltip();
this._helpTooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
}
});
}
},
_initBinds: function () {
var hide = function () {
this._operatorsListView.hide();
this._popupManager.untrack();
}.bind(this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
},
_onOperatorsChange: function (data) {
this.model.set(this.options.keyAttr, data);
this._setOperatorTemplate();
this.$('.js-button').focus();
this.trigger('change', this);
},
_getOperatorsTextValue: function () {
var value = this.model.get(this.options.keyAttr);
if (value && value.operator) {
return value.operator.toUpperCase() + (value.attribute ? '(' + value.attribute + ')' : '');
} else {
return '';
}
},
_onButtonClick: function (ev) {
this._operatorsListView.toggle();
this._operatorsListView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
},
_onButtonKeyDown: function (ev) {
if (ev.which === ENTER_KEY_CODE) {
ev.preventDefault();
if (!this._operatorsListView.isVisible()) {
ev.stopPropagation();
this._operatorsListView.toggle();
this._popupManager.track();
} else {
this._popupManager.untrack();
}
}
},
getValue: function () {
var data = this.model.get(this.options.keyAttr);
return _.omit(data, 'visible');
},
setValue: function (value) {
var textValue = this._getOperatorsTextValue();
this.value = textValue;
this._setOperatorTemplate();
},
_removeTooltip: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
},
remove: function () {
this._removeTooltip();
this._popupManager && this._popupManager.destroy();
this._operatorsListView && this._operatorsListView.clean();
Backbone.Form.editors.Base.prototype.remove.call(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,8 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis
<% if (disabled) { %> is-disabled <% } %>
<% if (!name) { %> is-empty <% } %>
<% if (help) { %> js-help<% } %>"
<% if (help) { %> data-tooltip="<%- help %>"<% } %>
tabindex="0">
<%- name || _t('components.backbone-forms.select.placeholder', { keyAttr: keyAttr }) %>
</div>

View File

@@ -0,0 +1,91 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var template = require('./radio.tpl');
var _ = require('underscore');
var $ = require('jquery');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
Backbone.Form.editors.Radio = Backbone.Form.editors.Radio.extend({
className: 'CDB-Text CDB-Size-medium u-flex u-alignCenter',
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.apply(this, arguments);
EditorHelpers.setOptions(this, opts);
},
render: function () {
this.setOptions(this.schema.options); // It comes from the default Select editor (not ours)
this._setHelp();
return this;
},
_setHelp: function () {
var containsHelp = _.find(this.schema.options, function (option) {
return option.help;
});
if (containsHelp) {
this._helpTooltip = this._createTooltip({
$el: this.$('.js-help')
});
}
},
_createTooltip: function (opts) {
return new TipsyTooltipView({
el: opts.$el || this.$el,
gravity: opts.gravity || 's',
className: opts.className || '',
offset: opts.offset || 0,
title: function () {
return opts.msg || $(this).data('tooltip');
}
});
},
getValue: function () {
var value = this.$('input[type=radio]:checked').val();
return (value === '') ? null : value;
},
_arrayToHtml: function (array) {
var selectedVal = this.form.model.get(this.key);
var items = _.map(array, function (option, index) {
var val = option.val;
var item = {
name: this.getName(),
value: (val === null) ? '' : val.toString(),
help: option.help,
id: this.id,
label: option.label,
className: option.className
};
// Can't be selected and disabled simultaneously
if (selectedVal === val) {
item.selected = true;
} else {
item.disabled = option.disabled;
}
return item;
}, this);
return template({
items: items
});
},
_destroyBinds: function () {},
remove: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,20 @@
<% _.each(items, function(item, index) { %>
<li class="u-flex u-alignCenter <%- (index === (items.length - 1)) ? '' : 'u-rSpace--xl' %>">
<input type="radio" class="CDB-Radio u-iBlock<% if (item.className) { %> <%- item.className %> <% } %>"
name="<%- item.name %>" value="<%- item.value %>" id="<%- item.id %>-<%- index %>"
<% if (item.selected) { %>
checked="checked"
<% } else if (item.disabled) { %>
disabled="disabled"
<% } %>
/>
<span class="u-rSpace CDB-Radio-face"></span>
<% if (item.help) { %>
<span class="js-help is-underlined u-lSpace" data-tooltip="<%- item.help %>">
<% } %>
<label for="<%- item.id %>-<%- index %>"><%- item.label %></label>
<% if (item.help) { %>
</span>
<% } %>
</li>
<% }); %>

View File

@@ -0,0 +1,193 @@
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var _ = require('underscore');
var CustomListView = require('builder/components/custom-list/custom-view');
var CustomListCollection = require('builder/components/custom-list/custom-list-multi-collection');
var selectedItemTemplate = require('./select-item.tpl');
var CustomListItemView = require('builder/components/custom-list/custom-list-multi-item-view');
var itemListTemplate = require('builder/components/custom-list/custom-list-item-with-checkbox.tpl');
var template = require('./select.tpl');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var ENTER_KEY_CODE = 13;
Backbone.Form.editors.MultiSelect = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'u-ellipsis Editor-formSelect',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
options: {
selectedItemTemplate: selectedItemTemplate,
itemListTemplate: itemListTemplate,
customListItemView: CustomListItemView
},
initialize: function (opts) {
EditorHelpers.setOptions(this, opts);
this.collection = new CustomListCollection(opts.schema.options);
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this._initViews();
this.setValue(this.model.get(opts.key));
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
this._initBinds();
},
_getLabel: function () {
var itemCount = _.compact(this.collection.pluck('selected')).length;
return _t('components.backbone-forms.select.selected', { count: itemCount });
},
_initViews: function () {
var isLoading = this.options.loading;
var isEmpty = !this.collection.length;
var isDisabled = !isEmpty ? this.options.disabled : true;
var name = this._getLabel();
var isNull = name === null;
var label = isNull ? 'null' : name;
this.$el.html(
template({
label: label,
keyAttr: this.options.keyAttr,
isDisabled: isDisabled,
isLoading: isLoading,
isEmpty: isEmpty,
isNull: isNull,
placeholder: null,
help: this.options.help
})
);
if (isDisabled) {
this.undelegateEvents();
}
this._listView = new CustomListView({
collection: this.collection,
showSearch: this.options.showSearch,
typeLabel: this.options.keyAttr,
itemTemplate: this.options.itemListTemplate,
itemView: this.options.customListItemView,
actions: [{
label: _t('components.backbone-forms.select.none'),
action: this._deselectAll.bind(this)
},
{
label: _t('components.backbone-forms.select.all'),
action: this._selectAll.bind(this)
}],
searchPlaceholder: 'Search'
});
this._listView.bind('hidden', this._onHide, this);
this._popupManager = new PopupManager(this.cid, this.$el, this._listView.$el);
this._popupManager.append(this.dialogMode);
},
_initBinds: function () {
var hide = function () {
this._listView.hide();
this._popupManager.untrack();
}.bind(this);
this.collection.bind('change:selected', _.debounce(this._onItemSelected, 50), this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
},
_selectAll: function () {
this.collection.each(function (model) {
model.set('selected', true);
});
},
_deselectAll: function () {
this.collection.each(function (model) {
model.set('selected', false);
});
},
_onItemSelected: function (mdl) {
this._renderButton(mdl).focus();
this.trigger('change', this);
},
_onButtonClick: function (ev) {
this._listView.toggle();
this._listView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
},
_onButtonKeyDown: function (ev) {
if (ev.which === ENTER_KEY_CODE) {
ev.preventDefault();
if (!this._listView.isVisible()) {
ev.stopPropagation();
this._listView.toggle();
this._popupManager.track();
} else {
this._popupManager.untrack();
}
}
},
_getSelectedValues: function () {
return this.collection.chain().map(function (m) { return m.get('selected') ? m.get('val') : null; }).compact().value();
},
getValue: function () {
var values = this._getSelectedValues();
if (values.length > 0) {
return values;
}
},
setValue: function (value) {
if (value) {
var selectedModel = this.collection.setSelected(value);
if (selectedModel) {
this._renderButton(selectedModel);
}
}
},
_renderButton: function (mdl) {
var button = this.$('.js-button');
var data = _.extend({}, mdl.attributes, { label: this._getLabel() });
var $html = this.options.selectedItemTemplate(data);
button
.removeClass('is-empty')
.html($html);
return button;
},
_onHide: function () {
if (this._getSelectedValues().length > 0) {
this.trigger('change', this);
}
},
remove: function () {
this._popupManager && this._popupManager.destroy();
this._listView && this._listView.clean();
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,12 @@
<% if (typeof isLoading != 'undefined' && isLoading) { %>
<div class="u-flex">
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<%- label %>
<% } %>

View File

@@ -0,0 +1,23 @@
<button type="button" class="CDB-ListDecoration-itemLink
<% if (isSelected) { %> is-selected <% } %> <% if (isDestructive) { %> u-alertTextColor <% } else { %> u-actionTextColor <% } %>"
title="<%- nodeTitle %> - <%- layerName %>">
<div class="u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-bSpace--s u-upperCase" style="color: <%- color %>;">
<%- layer_id %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- color %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-medium u-lSpace u-ellipsLongText">
<%= nodeTitle %>
</span>
<span class="CDB-Text CDB-Size-medium u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
</button>

View File

@@ -0,0 +1,55 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
module.exports = CoreView.extend({
options: {
template: require('./select-layer-list-item.tpl')
},
className: 'CDB-ListDecoration-item CustomList-item js-listItem',
tagName: 'li',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave',
'click': '_onClick'
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.append(
this.options.template(
_.extend({
typeLabel: this.options.typeLabel,
isSelected: this.model.get('selected'),
isDestructive: this.model.get('destructive'),
layerName: this.model.get('layerName'),
nodeTitle: this.model.get('nodeTitle'),
color: this.model.get('color'),
layer_id: this.model.get('layer_id'),
isSourceType: this.model.get('isSourceType')
})
)
);
this.$el.attr('data-val', this.model.getValue());
return this;
},
_onMouseLeave: function () {
this.$el.removeClass('is-highlighted');
},
_onMouseEnter: function () {
this.$el.addClass('is-highlighted');
},
_onClick: function (e) {
e.stopPropagation();
this.model.set('selected', true);
}
});

View File

@@ -0,0 +1,19 @@
<div class="u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-bSpace--s u-upperCase" style="color: <%- color %>;">
<%- layer_id %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- color %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-medium u-lSpace">
<%= nodeTitle %>
</span>
<span class="CDB-Text CDB-Size-medium u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>

View File

@@ -0,0 +1,13 @@
<% if (status === 'fetching') { %>
<div class="InputColorCategory-loader CDB-Box-modal InputColorCategory-loader">
<div class="CDB-LoaderIcon is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
</div>
<% } else if (status === 'error') { %>
<div class="u-flex u-alignCenter u-justifyCenter CDB-Text CDB-Size-medium u-bSpace--m u-tSpace--m u-errorTextColor"><%- _t('components.backbone-forms.select.error', {
type: type
}) %></div>
<% } %>

View File

@@ -0,0 +1,168 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var CustomView = require('builder/components/custom-list/custom-view');
var DropdownOverlayView = require('builder/components/dropdown-overlay/dropdown-overlay-view');
var statusTemplate = require('./select-list-view-states.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'collection',
'itemTemplate',
'itemView'
];
module.exports = CoreView.extend({
module: 'components:form-components:editors:select:select-list-view',
className: 'CustomList',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._selectModel = this.options.selectModel;
this.model = new Backbone.Model({
visible: false
});
if (this.options.mouseOverAction) {
this._mouseOverAction = this.options.mouseOverAction;
}
if (this.options.mouseOutAction) {
this._mouseOutAction = this.options.mouseOutAction;
}
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this._renderListSection();
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:visible', function (mdl, isVisible) {
isVisible ? this.render() : this.clearSubViews();
this._toggleVisibility();
this.trigger('change:visible');
});
if (this.collection.isAsync()) {
this.listenTo(this.collection.stateModel, 'change:state', this._renderListSection);
}
},
_createListView: function () {
if (this._listView) {
this._listView.clean();
this.removeView(this._listView);
}
this._listView = new CustomView({
className: 'CDB-Box-modal CustomList--inner',
collection: this.collection,
showSearch: this.options.showSearch,
allowFreeTextInput: this.options.allowFreeTextInput,
typeLabel: this.options.typeLabel,
itemTemplate: this._itemTemplate,
itemView: this._itemView,
position: this.options.position,
searchPlaceholder: this.options.searchPlaceholder,
selectModel: this._selectModel,
mouseOverAction: this._mouseOverAction,
mouseOutAction: this._mouseOutAction
});
this.addView(this._listView);
this._listView.show();
this.el.appendChild(this._listView.render().el);
},
_createStatusView: function (status) {
var el = statusTemplate({
status: status,
type: this.options.typeLabel
});
this.$el.html(el);
},
_renderListSection: function () {
var status = this._getStatus();
if (status === 'fetched') {
this._createListView();
} else {
this._createStatusView(status);
}
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
_renderOverlay: function () {
var closestModalDialog = this.$el.closest('.Dialog');
this._dropdownOverlay = new DropdownOverlayView({
container: closestModalDialog.length ? closestModalDialog : undefined,
onClickAction: this.hide.bind(this),
visible: true
});
this.addView(this._dropdownOverlay);
},
_destroyOverlay: function () {
this._dropdownOverlay && this._dropdownOverlay.clean() && this.removeView(this._dropdownOverlay);
},
isVisible: function () {
return this.model.get('visible');
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.isVisible());
if (this.isVisible()) {
this._renderOverlay();
} else {
this._destroyOverlay();
}
},
_getStatus: function () {
if (this.collection.isAsync()) {
return this.collection.stateModel.get('state');
} else {
return 'fetched';
}
},
_destroyBinds: function () {
this.stopListening(this.collection);
},
_onMouseOver: function () {
this._mouseOverAction && this._mouseOverAction();
},
_onMouseOut: function () {
this._mouseOutAction && this._mouseOutAction();
},
remove: function () {
this._listView && this._listView.clean();
this._dropdownOverlay && this._dropdownOverlay.clean();
this._destroyBinds();
}
});

View File

@@ -0,0 +1,42 @@
var Backbone = require('backbone');
Backbone.Form.editors.SelectPlaceholder = Backbone.Form.editors.Select.extend({
initialize: function (opts) {
Backbone.Form.editors.Select.prototype.initialize.call(this, opts);
},
_renderButton: function (model) {
Backbone.Form.editors.Select.prototype._renderButton.call(this, model);
if (this.options.forcePlaceholder) {
this._getButton().addClass('is-empty');
}
},
_getLabel: function () {
if (this.options.forcePlaceholder) {
return this.options.placeholder;
}
return Backbone.Form.editors.Select.prototype._getLabel.call(this);
},
_hasValue: function () {
if (this.options.forcePlaceholder) {
return false;
}
return Backbone.Form.editors.Select.prototype._hasValue.call(this);
},
_getButtonTemplateData: function (model) {
var data = Backbone.Form.editors.Select.prototype._getButtonTemplateData.call(this, model);
if (this.options.forcePlaceholder) {
data.label = this.options.placeholder;
}
return data;
}
});

View File

@@ -0,0 +1,326 @@
var $ = require('jquery');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var _ = require('underscore');
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var selectedItemTemplate = require('./select-item.tpl');
var CustomListItemView = require('builder/components/custom-list/custom-list-item-view');
var itemListTemplate = require('builder/components/custom-list/custom-list-item.tpl');
var template = require('./select.tpl');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var SelectListView = require('./select-list-view');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var ENTER_KEY_CODE = 13;
Backbone.Form.editors.Select = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'u-ellipsis Editor-formSelect',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
options: {
selectedItemTemplate: selectedItemTemplate,
itemListTemplate: itemListTemplate,
customListItemView: CustomListItemView
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.template = opts.template || template;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
if (this.options.mouseOverAction) {
this._mouseOverAction = this.options.mouseOverAction;
}
if (this.options.mouseOutAction) {
this._mouseOutAction = this.options.mouseOutAction;
}
if (this.options.editorAttrs && this.options.editorAttrs.help) {
this._help = this.options.editorAttrs.help;
}
if (this.options.options != null) {
this.collection = new CustomListCollection(this.options.options);
} else {
this.collection = this.options.collection;
}
if (this.collection.isAsync === undefined) {
throw new Error('collection must implement isAsync method.');
}
this._initViews();
this.setValue(this.model.get(this.options.keyAttr));
this._initBinds();
},
render: function () {
var isEmpty = !this.collection.length;
var isNull = !this._hasValue();
this._isDisabled = !isEmpty ? this.options.disabled : true;
var placeholder = this._getPlaceholder(this._isDisabled);
var label = isNull ? placeholder : this._getLabel();
var isLoading = this._isLoading();
this.$el.html(this.template({
keyAttr: this.options.keyAttr,
isEmpty: isEmpty,
label: label,
isDisabled: this._isDisabled,
isNull: isNull,
isLoading: isLoading,
help: this._help || ''
}));
// we are replacing the html, so we need to re append if nested mode
if (this.dialogMode === DialogConstants.Mode.NESTED) {
this._popupManager.append(this.dialogMode);
}
if (!isLoading) {
this._renderSelected();
}
return this;
},
_initBinds: function () {
var hide = function () {
this._listView.hide();
this._popupManager.untrack();
this._onToggleSelected();
}.bind(this);
this.applyESCBind(hide);
this.listenTo(this.collection, 'change:selected', this._onItemSelected);
this.listenTo(this._listView, 'change:visible', this._onToggleSelected);
if (this.collection.isAsync()) {
this.listenTo(this.collection.stateModel, 'change:state', this.render);
}
},
_initViews: function () {
this._listView = new SelectListView({
collection: this.collection,
showSearch: this.options.showSearch,
allowFreeTextInput: this.options.allowFreeTextInput,
typeLabel: this.options.keyAttr,
itemTemplate: this.options.itemListTemplate,
itemView: this.options.customListItemView,
position: this.options.position,
searchPlaceholder: this.options.searchPlaceholder,
selectModel: this.options.defaultValue && this.model,
mouseOverAction: this._mouseOverAction,
mouseOutAction: this._mouseOutAction
});
this._popupManager = new PopupManager(this.cid, this.$el, this._listView.$el);
this._popupManager.append(this.dialogMode);
},
_getPlaceholder: function (isDisabled) {
var keyAttr = this.options.keyAttr;
var placeholder;
if (isDisabled) {
placeholder = this.options.disabledPlaceholder || _t('components.backbone-forms.select.disabled-placeholder', { keyAttr: keyAttr });
} else {
placeholder = this.options.placeholder || _t('components.backbone-forms.select.placeholder', { keyAttr: keyAttr });
}
return placeholder;
},
_hasValue: function () {
var name = this.model.get(this.options.keyAttr);
return name != null && name !== '';
},
_getLabel: function () {
var name = this.model.get(this.options.keyAttr);
var mdl = this.collection.findWhere({val: name});
return mdl && mdl.getName() || name || '';
},
_isLoading: function () {
var isLoading = this.options.loading;
if (this.collection.isAsync()) {
isLoading = this.collection.stateModel.get('state') === 'fetching';
}
return isLoading;
},
_destroyBinds: function () {
this.stopListening(this.collection);
Backbone.Form.editors.Base.prototype._destroyBinds.call(this);
},
_onItemSelected: function (model) {
this._listView.hide();
this._popupManager.untrack();
this._renderButton(model);
this.trigger('change', this);
},
_onButtonClick: function () {
if (this._isDisabled) {
return;
}
this._listView.toggle();
this._listView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
this._onToggleSelected();
},
_onButtonKeyDown: function (event) {
if (this._isDisabled) {
return;
}
if (event.which === ENTER_KEY_CODE) {
event.preventDefault();
if (!this._listView.isVisible()) {
event.stopPropagation();
this._listView.toggle();
} else {
this._popupManager.track();
}
}
},
getValue: function () {
var item = this.collection.getSelectedItem();
if (item) {
return item.getValue();
} else {
return this.value;
}
},
setValue: function (value) {
var selectedModel = this.collection.setSelected(value);
if (selectedModel) {
this._renderButton(selectedModel);
} else {
this.render();
}
this.value = value;
},
_renderSelected: function () {
var selectedModel = this.collection.getSelectedItem();
if (selectedModel) {
this._renderButton(selectedModel);
}
},
_getButton: function () {
return this.$('.js-button');
},
_getButtonTemplateData: function (model) {
return _.extend({ isSourceType: false }, model.attributes, { label: model.getName() });
},
_renderButton: function (model) {
var button = this._getButton();
var data = this._getButtonTemplateData(model);
var $html = this.options.selectedItemTemplate(data);
button
.removeClass('is-empty')
.html($html);
this._initButtonBinds();
if (this._help) {
this._removeTooltip();
this._helpTooltip = new TipsyTooltipView({
el: this.$('.js-help'),
gravity: 'w',
title: function () {
return $(this).data('tooltip');
}
});
}
return button;
},
_removeTooltip: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
},
_initButtonBinds: function () {
var button = this._getButton();
button
.on('mouseover', this._onMouseOver.bind(this))
.on('mouseout', this._onMouseOut.bind(this));
},
_destroyButtonBinds: function () {
var button = this._getButton();
button
.off('mouseover', this._onMouseOver.bind(this))
.off('mouseout', this._onMouseOut.bind(this));
},
remove: function () {
this._removeTooltip();
this._popupManager && this._popupManager.destroy();
this._listView && this._listView.clean();
this._destroyButtonBinds();
this._destroyBinds();
Backbone.Form.editors.Base.prototype.remove.call(this);
},
_onToggleSelected: function () {
var visible = this._listView.isVisible();
this.$el.toggleClass('is-active', visible);
if (!visible) {
this._getButton().blur();
}
},
_onMouseOver: function () {
this._mouseOverAction && this._mouseOverAction();
},
_onMouseOut: function () {
this._mouseOutAction && this._mouseOutAction();
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,22 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis
<% if (isDisabled) { %> is-disabled <% } %>
<% if (!label) { %> is-empty <% } %>
<% if (isNull) { %> is-empty <% } %>
<% if (help) { %> js-help<% } %>"
<% if (help) { %> data-tooltip="<%- help %>"<% } %>
tabindex="0">
<% if (isLoading) { %>
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<% if (isEmpty && isNull) { %>
<%- _t('components.backbone-forms.select.empty') %>
<% } else { %>
<%- label %>
<% } %>
<% } %>
</div>

View File

@@ -0,0 +1,69 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var template = require('./select.tpl');
Backbone.Form.editors.Suggest = Backbone.Form.editors.Select.extend({
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.template = opts.template || template;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this._setupCollection();
this._initViews();
this.setValue(this.model.get(this.options.keyAttr));
this._initBinds();
},
_setupCollection: function () {
var item = { label: this.value, val: this.value };
this.collection = new CustomListCollection(this.options.options);
if (this.options.editorAttrs && this.options.editorAttrs.collectionData) {
var categories = _.map(this.options.editorAttrs.collectionData, function (data) {
return { label: data, val: data };
});
this.collection.reset(categories);
}
if (this.value && !this.collection.findWhere(item)) {
this.collection.add(item);
}
},
_initBinds: function () {
var hide = function () {
this._listView.hide();
this._popupManager.untrack();
}.bind(this);
this.collection.bind('change:selected', this._onItemSelected, this);
this.collection.bind('reset', this.render, this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
},
_renderButton: function (model) {
var button = this.$('.js-button');
var name = model.getName();
var isNull = name === null || name === 'null';
var label = isNull ? 'null' : name;
var data = _.extend({}, model.attributes, { label: label });
var $html = this.options.selectedItemTemplate(data);
button
.html($html)
.toggleClass('is-empty', isNull);
return button;
}
});

View File

@@ -0,0 +1,146 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var template = require('./size-by-value-content-view.tpl');
var DEFAULT_RANGE = [1, 5];
module.exports = CoreView.extend({
events: {
'click .js-back': '_onClickBack',
'click .js-bins': '_onClickBins',
'click .js-quantification': '_onClickQuantification'
},
render: function () {
this.clearSubViews();
this._removeForm();
this.$el.empty();
this.$el.append(
template({
bins: this.model.get('bins'),
attribute: this.model.get('attribute'),
quantification: this.model.get('quantification')
})
);
this._initForm();
return this;
},
_initForm: function () {
if (this._formView) this._formView.remove();
var range = this._getRangeOrCalculateItIfNeeded();
this._formView = this._createFormView(range);
this._renderFormView();
},
_renderFormView: function () {
this.$('.js-content').append(this._formView.render().$el);
},
_getRangeOrCalculateItIfNeeded: function () {
var min, max;
var fixedValue = this.model.get('fixed');
var rangeValues = this.model.get('range');
if (rangeValues) {
min = +rangeValues[0];
max = +rangeValues[1];
}
/* if we come from a fixed value, we need to
calculate the range values based on this */
if (fixedValue) {
var rangeFromFixedValue = this._calculateRangeFromFixed(fixedValue);
min = rangeFromFixedValue[0];
max = rangeFromFixedValue[1];
this.model.set('range', rangeFromFixedValue); // changes are propagated!
this.model.unset('fixed'); // once we have a range, everything is ready to map by value
}
return [min, max];
},
_createFormView: function (range) {
var min = range[0];
var max = range[1];
var formModel = new Backbone.Model({ min: min, max: max });
var getNumberType = this._getNumberType.bind(this);
formModel.schema = { min: getNumberType(), max: getNumberType() };
formModel.bind(
'change',
function (input) {
this.model.set('range', [+input.get('min'), +input.get('max')]);
},
this
);
var formView = new Backbone.Form({
className: 'Editor-boxList',
model: formModel
});
formView.bind('change', function () {
this.commit();
});
return formView;
},
_calculateRangeFromFixed: function (fixed, percent) {
percent = percent || 30;
var span = this.options.max - this.options.min;
var delta = fixed / span;
var min = Math.floor(Math.max(this.options.min, fixed - percent * delta));
var max = Math.floor(Math.min(this.options.max, fixed + percent * delta));
min = Math.max(min, DEFAULT_RANGE[0]);
max = Math.max(max, DEFAULT_RANGE[1]);
return [min, max];
},
_getNumberType: function () {
return {
type: 'Number',
validators: [
'required',
{
type: 'interval',
min: this.options.min,
max: this.options.max
}
]
};
},
_removeForm: function () {
this._formView && this._formView.remove();
},
_onClickBack: function (e) {
this.killEvent(e);
this.trigger('back', this);
},
_onClickQuantification: function (e) {
this.killEvent(e);
this.trigger('selectQuantification', this);
},
_onClickBins: function (e) {
this.killEvent(e);
this.trigger('selectBins', this);
},
clean: function () {
this._removeForm();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,39 @@
<div class="CDB-Box-modalHeader">
<ul class="CDB-Box-modalHeaderItem CDB-Box-modalHeaderItem--block CDB-Box-modalHeaderItem--paddingHorizontal">
<li class="InputColor-modalHeader CDB-ListDecoration-item CDB-ListDecoration-itemPadding--vertical CDB-Text CDB-Size-medium u-secondaryTextColor">
<div class="u-flex u-alignStart u-ellipsis">
<button class="u-rSpace u-actionTextColor js-back">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large"></i>
</button>
<div class="u-ellipsis test-attribute">
<%- attribute %>
</div>
</div>
</li>
<li class="CDB-ListDecoration-item CDB-ListDecoration-itemPadding--vertical CDB-Text CDB-Size-medium u-secondaryTextColor">
<ul class="u-flex u-justifySpace">
<li class="u-flex test-bins">
<%- bins %> <%- _t('form-components.editors.fill.input-ramp.buckets', { smart_count: bins }) %>
<button class="CDB-Shape u-lSpace js-bins">
<div class="CDB-Shape-threePoints is-horizontal is-blue is-small">
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
</div>
</button>
</li>
<li class="u-flex test-quantification">
<%- _t('form-components.editors.fill.quantification.methods.' + quantification) %>
<button class="CDB-Shape u-lSpace js-quantification">
<div class="CDB-Shape-threePoints is-horizontal is-blue is-small">
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
</div>
</button>
</li>
</ul>
</li>
</ul>
</div>
<div class="js-content"></div>

View File

@@ -0,0 +1,304 @@
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var template = require('./size-by-value-view.tpl');
var DialogModel = require('builder/components/dialog/dialog-model');
var DialogView = require('builder/components/dialog/dialog-view');
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
var ColumnListView = require('builder/components/custom-list/column-list/column-list-view');
var columnListQuantificationMethodItemTemplate = require('builder/components/custom-list/column-list/column-list-quantification-method-item.tpl');
var SizeByValueContentView = require('./size-by-value-content-view');
var PopupManager = require('builder/components/popup-manager');
var FillConstants = require('builder/components/form-components/_constants/_fill');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'columns',
'popupConfig'
];
var COLUMN_PANE_INDEX = 0;
var MAIN_PANE_INDEX = 1;
var QUANTIFICATION_PANE_INDEX = 2;
var BINS_PANE_INDEX = 3;
module.exports = CoreView.extend({
className: 'Form-StyleByValue u-ellipsis',
events: {
'click .js-button': '_showByValueDialog',
'click .js-back': '_onClickBack'
},
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._settings = FillConstants.Settings.NUMBER;
this._setupModel();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this._initViews();
return this;
},
afterRender: function () {
this._openPopupForColumn();
},
_openPopupForColumn: function () {
var columnSelected = this.model.get('attribute');
if (_.isUndefined(columnSelected)) {
var self = this;
this.timeoutId = setTimeout(function () {
self.timeoutId = null;
self._showByValueDialog();
}, 200);
}
},
_setupModel: function () {
var options = {};
if (!this.model.get('quantification')) {
var quantifications = this._settings.quantifications;
options.quantification =
quantifications.items[quantifications.defaultIndex];
}
var modelBins = this.model.get('bins');
var defaultBins = this._settings.bins;
if (!modelBins) {
options.bins = defaultBins.items[defaultBins.defaultIndex];
}
if (+modelBins > +_.last(defaultBins.items)) {
options.bins = _.last(defaultBins.items);
}
this.model.set(options);
this.listenTo(this.model, 'change:attribute change:range', this.render);
},
_initViews: function () {
this._initDialog();
this._initPopup();
this._initInputColumn();
},
_initDialog: function () {
if (this._dialogView) return;
var dialogModel = new DialogModel();
this.listenToOnce(dialogModel, 'destroy', function () {
this._dialogView = null;
this.stopListening(dialogModel);
});
this._dialogView = new DialogView({
model: dialogModel
});
},
_initPopup: function () {
this._popupManager = new PopupManager(
this._popupConfig.cid,
this._popupConfig.$el,
this._dialogView.$el
);
},
_initInputColumn: function () {
var columnSelected = this.model.get('attribute');
var rangeSelected = this.model.get('range');
var labelColumn = columnSelected || _t('form-components.editors.style.select-by-column');
var labelRange = rangeSelected ? rangeSelected[0] + ' - ' + rangeSelected[1] : '';
this.$el.append(template({
columnSelected: !!columnSelected,
label: labelColumn,
rangeSelected: !!rangeSelected,
range: labelRange
}));
},
removeDialog: function () {
this._dialogView.clean();
this._popupManager.untrack();
},
removePopupManager: function () {
this._popupManager.destroy();
},
_createStackView: function () {
var stackLayoutView = new StackLayoutView({
collection: new Backbone.Collection([
{ createStackView: this._createColumnsView.bind(this) },
{ createStackView: this._createSizeValueContentView.bind(this) },
{ createStackView: this._createQuantificationView.bind(this) },
{ createStackView: this._createBinsView.bind(this) }
])
});
stackLayoutView.model.set('position', this._getDialogStepPosition());
return stackLayoutView;
},
_showByValueDialog: function () {
this._dialogView.model.set('createContentView', this._createStackView.bind(this));
this._dialogView.render();
this._dialogView.show();
this._popupManager.append(this._popupConfig.mode);
this._popupManager.track();
},
_getDialogStepPosition: function () {
var position = MAIN_PANE_INDEX;
if (!this.model.get('attribute')) {
position = COLUMN_PANE_INDEX;
} else if (!this.model.get('quantification')) {
position = QUANTIFICATION_PANE_INDEX;
}
return position;
},
_createColumnsView: function (stackLayoutModel, opts) {
var view = new ColumnListView({
stackLayoutModel: stackLayoutModel,
columns: this._columns.filter(function (column) {
return column.type === 'number';
}),
showSearch: true,
typeLabel: 'column'
});
view.bind(
'selectItem',
function (item) {
this.model.set('attribute', item.get('val'));
var step = MAIN_PANE_INDEX;
if (!this.model.get('quantification')) {
step = QUANTIFICATION_PANE_INDEX;
}
stackLayoutModel.goToStep(step);
},
this
);
return view;
},
_createSizeValueContentView: function (stackLayoutModel, opts) {
var view = new SizeByValueContentView({
stackLayoutModel: stackLayoutModel,
model: this.model,
min: this.options.min,
max: this.options.max
});
view.bind(
'back',
function (value) {
stackLayoutModel.prevStep();
},
this
);
view.bind(
'selectQuantification',
function (value) {
stackLayoutModel.goToStep(QUANTIFICATION_PANE_INDEX);
},
this
);
view.bind(
'selectBins',
function (value) {
stackLayoutModel.goToStep(BINS_PANE_INDEX);
},
this
);
return view;
},
_createQuantificationView: function (stackLayoutModel, opts) {
var view = new ColumnListView({
headerTitle: _t('form-components.editors.fill.quantification.title'),
stackLayoutModel: stackLayoutModel,
columns: this._settings.quantifications.items,
itemTemplate: columnListQuantificationMethodItemTemplate,
showSearch: false
});
view.bind(
'selectItem',
function (item) {
this.model.set('quantification', item.get('val'));
stackLayoutModel.prevStep();
},
this
);
view.bind(
'back',
function (value) {
stackLayoutModel.prevStep();
},
this
);
return view;
},
_createBinsView: function (stackLayoutModel, opts) {
var view = new ColumnListView({
headerTitle: _t('form-components.editors.fill.bins'),
stackLayoutModel: stackLayoutModel,
columns: this._settings.bins.items
});
view.bind(
'selectItem',
function (item) {
this.model.set('bins', item.get('val'));
stackLayoutModel.goToStep(MAIN_PANE_INDEX);
},
this
);
view.bind(
'back',
function (value) {
stackLayoutModel.goToStep(MAIN_PANE_INDEX);
},
this
);
return view;
},
_onClickBack: function (e) {
this.killEvent(e);
},
clean: function () {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,14 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis <% if (!columnSelected) { %> u-altTextColor <% } %>" tabindex="0">
<ul class="Form-StyleByValue--column CDB-OptionInput-container CDB-OptionInput-container--border">
<li class="u-ellipsis by-column-name">
<%- label %>
</li>
<% if (rangeSelected) { %>
<li class="by-column-range">
<%- range %>
</li>
<% } %>
</ul>
</div>

View File

@@ -0,0 +1,61 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
module.exports = CoreView.extend({
render: function () {
this.clearSubViews();
this._removeForm();
this.$el.empty();
this._initForm();
return this;
},
_initForm: function () {
this._formModel = new Backbone.Model({
value: this.model.get('fixed')
});
this._formModel.schema = {
value: {
type: 'Number',
title: '',
validators: [
'required',
{
type: 'interval',
min: this.options.min,
max: this.options.max,
step: this.options.step
}
]
}
};
this._formModel.bind(
'change',
function (input) {
this.model.set('fixed', input.get('value'));
},
this
);
this._formView = new Backbone.Form({
model: this._formModel
});
this._formView.bind('change', function () {
this.commit();
});
this.$el.append(this._formView.render().$el);
},
_removeForm: function () {
this._formView && this._formView.remove();
},
clean: function () {
this._removeForm();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,239 @@
var _ = require('underscore');
var Backbone = require('backbone');
var tabPaneTemplate = require('builder/components/tab-pane/tab-pane.tpl');
var createRadioLabelsTabPane = require('builder/components/tab-pane/create-radio-labels-tab-pane');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var SizeFixedView = require('./size-fixed-view');
var SizeByValueView = require('./size-by-value-view');
var FillConstants = require('builder/components/form-components/_constants/_fill');
Backbone.Form.editors.Size = Backbone.Form.editors.Base.extend({
className: 'Form-InputSize CDB-Text',
events: {
focus: function () {
this.trigger('focus', this);
},
blur: function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this._sizeModel = new Backbone.Model(this.model.get(opts.key));
this._initBinds();
this._initViews();
},
_initBinds: function () {
this._updateSizeModelOnChangeBind();
this.applyESCBind(this._removeDialogs);
this.applyClickOutsideBind(this._removeDialogs);
},
_updateSizeModelOnChangeBind: function () {
var self = this;
this._sizeModel.bind('change', function () {
self.model.set(self.key, self._sizeModel.toJSON());
}, this);
},
_removeDialogs: function () {
this._valueView && this._valueView.removeDialog();
},
_removePopupManagers: function () {
this._valueView && this._valueView.removePopupManager();
},
_initViews: function () {
this._tabPaneTabs = this._getTabPanes();
this._setSelectedTab();
this._tabPaneView = createRadioLabelsTabPane(
this._tabPaneTabs,
this._getTabPaneOptions()
);
this.listenTo(
this._tabPaneView.collection,
'change:selected',
this._onChangeTabPaneViewTab
);
this.$el.append(this._tabPaneView.render().$el);
},
_getTabPanes: function () {
var tabPaneTabs = [];
var attrs = this.options.editorAttrs;
if (attrs && attrs.hidePanes) {
if (!_.contains(attrs.hidePanes, FillConstants.Panes.FIXED)) tabPaneTabs.push(this._buildFixedPane());
if (!_.contains(attrs.hidePanes, FillConstants.Panes.BY_VALUE)) tabPaneTabs.push(this._buildByValuePane());
} else {
tabPaneTabs = [this._buildFixedPane(), this._buildByValuePane()];
}
return tabPaneTabs;
},
_setSelectedTab: function () {
var selectedIndex = this._getSelectedTabPaneIndex();
this._tabPaneTabs[selectedIndex].selected = true;
},
_getTabPaneOptions: function () {
var options = {
tabPaneOptions: {
template: tabPaneTemplate,
tabPaneItemOptions: {
tagName: 'li',
klassName: 'CDB-NavMenu-item'
}
},
tabPaneItemLabelOptions: {
tagName: 'div',
className: 'CDB-Text CDB-Size-medium'
}
};
return options;
},
_buildFixedPane: function () {
var self = this;
var geometryName = this.options.editorAttrs.geometryName;
var fixedPane = {
name: FillConstants.Panes.FIXED,
label: _t('form-components.editors.fill.input-number.fixed'),
tooltip: _t('editor.style.tooltips.size.fixed-tab', { type: geometryName }),
tooltipGravity: 's',
createContentView: function () {
return self._generateFixedContentView();
}
};
return fixedPane;
},
_buildByValuePane: function () {
var self = this;
var geometryName = this.options.editorAttrs.geometryName;
var valuePane = {
name: FillConstants.Panes.BY_VALUE,
label: _t('form-components.editors.fill.input-number.by-value'),
tooltip: _t('editor.style.tooltips.size.by-value-tab', { type: geometryName }),
tooltipGravity: 's',
createContentView: function () {
return self._generateByValueContentView();
}
};
return valuePane;
},
_getSelectedTabPaneIndex: function () {
var FIXED_TAB_PANE = 0;
var BY_VALUE_PANE = 1;
var hasRange = this._sizeModel.get('range');
var thereIsByValuePane = this._tabPaneTabs.length > 1;
return hasRange && thereIsByValuePane ? BY_VALUE_PANE : FIXED_TAB_PANE;
},
_onChangeTabPaneViewTab: function () {
var selectedTabPaneName = this._tabPaneView.getSelectedTabPaneName();
if (selectedTabPaneName === FillConstants.Panes.FIXED) {
this._updateFixedValue();
} else {
this._updateRangeValue();
}
this.trigger('change', selectedTabPaneName, this);
},
_updateFixedValue: function () {
var range = this._sizeModel.get('range');
if (range) {
// when coming from range calculate the average...
var avg = 0.5 * (+range[0] + +range[1]);
this._sizeModel.set('fixed', avg);
this._sizeModel.unset('range');
}
},
_updateRangeValue: function () {
var fixed = this._sizeModel.get('fixed');
if (
fixed !== null &&
!_.isUndefined(fixed) &&
this._sizeModel.get('attribute')
) {
var editorAttrs = this.options.editorAttrs;
var range = editorAttrs && editorAttrs.defaultRange || [fixed, fixed];
this._sizeModel.set('range', range);
this._sizeModel.unset('fixed');
}
},
_generateFixedContentView: function () {
var editorAttrs = this.options.editorAttrs;
this._fixedView = new SizeFixedView({
model: this._sizeModel,
min: editorAttrs && editorAttrs.min || FillConstants.Size.DefaultInput100.MIN,
max: editorAttrs && editorAttrs.max || FillConstants.Size.DefaultInput100.MAX,
step: editorAttrs && editorAttrs.step || FillConstants.Size.DefaultInput100.STEP
});
return this._fixedView;
},
_generateByValueContentView: function () {
var editorAttrs = this.options.editorAttrs;
this._valueView = new SizeByValueView({
model: this._sizeModel,
columns: this.schema.options,
min: editorAttrs && editorAttrs.min || FillConstants.Size.DefaultInput100.MIN,
max: editorAttrs && editorAttrs.max || FillConstants.Size.DefaultInput100.MAX,
popupConfig: {
cid: this.cid,
$el: this.$el,
mode: this.options.dialogMode
}
});
return this._valueView;
},
focus: function () {
if (this.hasFocus) return;
this.$('.js-menu').focus();
},
blur: function () {
if (!this.hasFocus) return;
this.$('.js-menu').blur();
},
getValue: function () {
return this._sizeModel.toJSON();
},
remove: function () {
this._removeDialogs();
this._removePopupManagers();
this._tabPaneView.clean();
this._valueView && this._valueView.clean();
this._fixedView && this._fixedView.clean();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,229 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var template = require('./slider.tpl');
var MutationObserver = window.MutationObserver;
var TICKS_PLACEHOLDER = 5;
var HANDLE_WIDTH = 12;
var DIMENSION = 160;
var INITIAL = {
HIGHEST: 'highest',
LOWEST: 'lowest'
};
Backbone.Form.editors.Slider = Backbone.Form.editors.Base.extend({
className: 'rangeslider--no-fill',
options: {
direction: 'horizontal',
initial: INITIAL.LOWEST
},
events: {
'change .js-slider': '_onValueChange',
focus: function () {
this.trigger('focus', this);
},
blur: function () {
this.trigger('blur', this);
}
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
if (!this.options.labels) {
throw new Error('labels is required');
}
if (this.options.values && this.options.labels.length !== this.options.values.length) {
throw new Error('values and labels should have the same length');
}
if (this.options.value !== undefined) {
this.value = this.options.value;
}
this._onValueChange = this._onValueChange.bind(this);
this._onSlideChange = this._onSlideChange.bind(this);
this._values = this.options.values;
this._labels = this.options.labels;
this._getInitialTickID();
this._updateUI();
},
render: function () {
var isDisabled = this.options.disabled || this._labels.length <= 0;
var max = this._labels.length === 0 ? TICKS_PLACEHOLDER - 1 : this._labels.length - 1;
var step = this._getStepPercentage();
this.$el.html(
template({
orientation: this.options.direction,
disabled: isDisabled,
min: 0,
max: max,
value: this.rangeIndex
})
);
this._renderSlider();
this._addTicks(step);
this._updateUI();
return this;
},
_getInitialTickID: function () {
var value = this.value;
if (this._values && this._values.length > 0) {
value = this._values.indexOf(this.value);
if (value === -1) {
value = this.options.initial === INITIAL.LOWEST ? 0 : this._values.length - 1;
}
} else if (!this.value) {
value = 0;
}
this.rangeIndex = value;
},
_getNumberOfValues: function () {
return (this._values && this._values.length) || 0;
},
_renderSlider: function () {
var element;
var field;
var onMutationObserver;
var observer;
var config = { subtree: true, childList: true };
if (!MutationObserver) {
this._initializeSlider();
} else {
element = document.body;
field = this.$('.js-slider').get(0);
onMutationObserver = function () {
if (element.contains(field)) {
this._initializeSlider();
observer.disconnect();
}
}.bind(this);
observer = new MutationObserver(onMutationObserver);
onMutationObserver();
observer.observe(element, config);
}
},
_initializeSlider: function () {
var numberOfValues = this._getNumberOfValues();
var init = function () {
this.$('.js-slider').rangeslider({
polyfill: false,
fillClass: 'rangesliderFill',
handleClass: 'rangesliderHandle',
onSlideEnd: this._onSlideChange
});
}.bind(this);
if (numberOfValues !== 1) {
!MutationObserver ? setTimeout(init, 0) : init();
}
this.$('.js-slider').toggle(numberOfValues !== 1);
},
_updateUI: function () {
this._updateLabel(this.rangeIndex);
this._highlightTick(this.rangeIndex);
},
_addTicks: function (step) {
var offset = HANDLE_WIDTH / 2;
var numberOfValues = this._getNumberOfValues();
var ticks = this._labels.length > 0 ? this._labels.length : TICKS_PLACEHOLDER;
_.each(_.range(ticks), function (tick, index) {
$('<div class="rangeslider-tick js-tick"></div>')
.css('left', (step * index + offset) + 'px')
.appendTo(this.$('.js-ticks'));
}, this);
this.$('.js-ticks').toggle(numberOfValues !== 1);
},
_highlightTick: function (tickID) {
this.$('.js-tick').removeClass('is-highlighted');
$(this.$('.js-tick').get(tickID)).addClass('is-highlighted');
},
_updateLabel: function (tickID) {
var label = this._labels[tickID];
if (this._labels.length <= 0) {
label = _t('form-components.editors.slide.no-values');
}
this.$('.js-label').text(label);
},
_getStepPercentage: function () {
var steps = this._getSteps();
var rangeWidth = DIMENSION - HANDLE_WIDTH;
return (rangeWidth / steps);
},
_getSteps: function () {
var steps = this._labels.length - 1;
if (steps < 0) {
steps = TICKS_PLACEHOLDER - 1;
}
return steps;
},
_onValueChange: function (e) {
var value = this.$('.js-slider').val();
this._onSlideChange(null, value);
},
_onSlideChange: function (position, value) {
this.rangeIndex = +value;
this.value = this._values && this._values.length > 0 ? this._values[this.rangeIndex] : this.rangeIndex;
this._updateUI(this.rangeIndex);
this.trigger('change', this);
},
getValue: function () {
var value = this._values && this._values.length > 0 ? this._values[this.rangeIndex] : null;
return this.value || value;
},
setValue: function (value) {
var index = this._values ? this._values.indexOf(value) : value;
this.$('.js-slider').val(index).change();
this.value = value;
},
_destroySlider: function () {
this.$('.js-slider').off('change', this._onValueChange);
this.$('.js-slider').rangeslider('destroy');
},
remove: function () {
this._destroySlider();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,13 @@
<input
class="js-slider"
type="range"
min="<%- min %>"
max="<%- max %>"
data-orientation="<%- orientation %>"
<% if (disabled) { %>
disabled="true"
<% } %>
value="<%- value %>"
>
<div class="rangeslider-ticks js-ticks"></div>
<div class="rangeslider-label CDB-Text CDB-Size-medium js-label"></div>

View File

@@ -0,0 +1,63 @@
var Backbone = require('backbone');
var _ = require('underscore');
require('jquery-ui');
Backbone.Form.editors.SortableList = Backbone.Form.editors.List.extend({
initialize: function () {
Backbone.Form.editors.List.prototype.initialize.apply(this, arguments);
},
render: function () {
Backbone.Form.editors.List.prototype.render.apply(this, arguments);
this._initSortable();
return this;
},
addItem: function () {
Backbone.Form.editors.List.prototype.addItem.apply(this, arguments);
this._updateEditability();
return this;
},
removeItem: function () {
Backbone.Form.editors.List.prototype.removeItem.apply(this, arguments);
this._updateEditability();
return this;
},
_initSortable: function () {
this.$list.sortable({
axis: 'y',
items: '.js-sortable',
tolerance: 'pointer',
containment: this.$list,
forceHelperSize: true,
forcePlaceholderSize: true,
update: this._onSortableUpdate.bind(this)
});
},
_onSortableUpdate: function (event, ui) {
var sorted = [];
var list = this.$list;
var items = this.items;
_.each(items, function (item) {
var index = item.$el.index(list.$el);
sorted[index] = item;
});
this.items = sorted;
this.commit();
},
_updateEditability: function () {
if (this.items.length > 1) {
this.$('.js-sortable').addClass('is-movable');
this.$('.js-editable').removeClass('is-hidden');
} else {
this.$('.js-sortable').removeClass('is-movable');
this.$('.js-editable').addClass('is-hidden');
}
}
});

View File

@@ -0,0 +1,19 @@
var Backbone = require('backbone');
module.exports = Backbone.Collection.extend({
removeTag: function (label) {
var m = this.findWhere({label: label});
m && this.remove(m);
},
addTag: function (label) {
var m = this.findWhere({label: label});
!m && this.add({label: label});
},
getValue: function () {
return this.map(function (mdl) {
return mdl.get('label');
});
}
});

View File

@@ -0,0 +1,16 @@
var CoreView = require('backbone/core-view');
module.exports = CoreView.extend({
tagName: 'li',
initialize: function (opts) {
if (!opts.label) throw new Error('label is required');
this._label = opts.label;
},
render: function () {
this.clearSubViews();
this.$el.html(this._label);
return this;
}
});

View File

@@ -0,0 +1,132 @@
var _ = require('underscore');
var Backbone = require('backbone');
var template = require('./taglist.tpl');
var TagView = require('./taglist-item-view');
var TagCollection = require('./taglist-collection');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
require('jquery');
require('jquery-ui');
require('tagit');
Backbone.Form.editors.Taglist = Backbone.Form.editors.Base.extend({
className: 'Form-tags CDB-Text',
events: {
'mouseover': '_onMouseOver',
'mouseout': '_onMouseOut'
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this._tagCollection = new TagCollection(this._normalize(opts.schema.options.tags));
this.isEditable = opts.schema.options.isEditable || opts.schema.options.isEditable === undefined;
},
render: function () {
this._initViews();
return this;
},
_initViews: function () {
var self = this;
var tagsPlaceholder = (!this.isEditable && this._tagCollection.length === 0)
? _t('components.taglist.none')
: _t('components.taglist.placeholder');
this.$el.html(template());
this._tagCollection.each(this._renderTag, this);
this.$('.js-tagsList').tagit({
allowSpaces: true,
caseSensitive: false,
placeholderText: tagsPlaceholder,
readOnly: !this.isEditable,
onBlur: function () {
self.isEditable && self.$el.removeClass('is-focus');
self.trigger('blur', self);
},
onFocus: function () {
self.isEditable && self.$el.removeClass('is-hover').addClass('is-focus');
self.trigger('focus', self);
},
preprocessTag: tag => tag.toLowerCase(),
afterTagAdded: self._onTagAdded.bind(self),
afterTagRemoved: self._onTagRemoved.bind(self)
});
},
_renderTag: function (model) {
var view = new TagView({
label: model.get('label')
});
this.$('.js-tagsList').append(view.render().el);
},
focus: function () {
if (this.hasFocus) return;
this.$el.focus();
},
blur: function () {
if (!this.hasFocus) return;
this.$el.blur();
},
getValue: function () {
return this._tagCollection.getValue();
},
setValue: function (tags) {
this._tagCollection.reset(this._normalize(tags));
this._destroyTagit();
this.render();
this.trigger('change', this);
},
_onMouseOver: function () {
!this.$el.hasClass('is-focus') && this.$el.addClass('is-hover');
},
_onMouseOut: function () {
!this.$el.hasClass('is-focus') && this.$el.removeClass('is-hover');
},
_onTagRemoved: function (e, ui) {
var tag = ui.tag.find('.tagit-label').text();
this._tagCollection.removeTag(tag);
this.trigger('change', this);
},
_onTagAdded: function (e, ui) {
var tag = ui.tag.find('.tagit-label').text();
this._tagCollection.addTag(tag);
this.trigger('change', this);
},
_normalize: function (tags) {
return _.map(tags, function (tag) {
return {
label: tag.trim()
};
});
},
_destroyTagit: function () {
// cannot call public methods before initilization
// checking ui-widget class does the trick
(this.$('.ui-widget').length > 0) && this.$('.js-tagsList').tagit('destroy');
},
remove: function () {
this._destroyTagit();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1 @@
<ul class="Form-tagsList js-tagsList"></ul>

View File

@@ -0,0 +1,110 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
Backbone.Form.editors.Text = Backbone.Form.editors.Text.extend({
className: 'CDB-InputText CDB-Text',
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
var schema = this.schema;
// Allow customising text type (email, phone etc.) for HTML5 browsers
var type = 'text';
if (schema && schema.editorAttrs && schema.editorAttrs.type) type = schema.editorAttrs.type;
if (schema && schema.dataType) type = schema.dataType;
if (this.options.editorAttrs && this.options.editorAttrs.help) {
this._help = this.options.editorAttrs.help;
}
this.$el.attr('type', type);
this.determineChange = _.debounce(this.determineChange, 200);
},
render: function () {
this.setValue(this.value);
this._toggleDisableState();
if (this._isCopyButtonEnabled()) {
this._toggleClipboardState();
}
if (this._help) {
this._removeTooltip();
if (!this.options.disabled) {
this._helpTooltip = new TipsyTooltipView({
el: this.$el,
gravity: 'w',
title: function () {
return this._help;
}.bind(this)
});
}
}
return this;
},
getValue: function () {
var val = this.$el.val();
return (val === '') ? null : val;
},
_toggleClipboardState: function () {
this.$el.toggleClass('Share-input-field u-ellipsis', this._isCopyButtonEnabled());
},
_togglePlaceholder: function () {
if (this.options.placeholder) {
this.$el.attr('placeholder', this.options.placeholder);
} else {
var placeholder = (this.value === null) ? 'null' : '';
this.$el.attr('placeholder', placeholder);
}
},
_toggleDisableState: function () {
if (this.options.disabled) {
this.$el.attr('readonly', '');
this.$el.attr('placeholder', '');
// if it's disabled AND has copy, leave just readonly
if (this._isCopyButtonEnabled()) {
this.$el.removeAttr('disabled');
}
} else {
this.$el.removeAttr('readonly');
this._togglePlaceholder();
}
this.$el.toggleClass('is-disabled', !!this.options.disabled);
},
_isCopyButtonEnabled: function () {
return !!this.options.hasCopyButton;
},
_removeTooltip: function () {
if (this._helpTooltip) {
this._helpTooltip.clean();
}
},
remove: function () {
this._removeTooltip();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,31 @@
var Backbone = require('backbone');
Backbone.Form.editors.TextArea = Backbone.Form.editors.Text.extend({
tagName: 'textarea',
className: 'CDB-Textarea',
render: function () {
this.setValue(this.value);
this._toggleDisableState();
return this;
},
getValue: function () {
var val = this.$el.val();
return (val === '') ? null : val;
},
_toggleDisableState: function () {
if (this.options.disabled) {
this.$el.attr('readonly', '');
this.$el.attr('placeholder', '');
} else {
this.$el.removeAttr('readonly');
this._togglePlaceholder();
}
this.$el.toggleClass('is-disabled', !!this.options.disabled);
}
});

Some files were not shown because too many files have changed in this diff Show More