Initial commit

This commit is contained in:
zhongjin
2020-06-13 18:34:34 +08:00
commit 52aaa9f15d
655 changed files with 96796 additions and 0 deletions
@@ -0,0 +1,4 @@
<div class="CDB-Overlay">
<button type="button" class="CDB-Attribution-button js-button">©</button>
<p class="CDB-Attribution-text js-text u-ellipsis"><%= attributions %></p>
</div>
@@ -0,0 +1,98 @@
var $ = require('jquery');
var _ = require('underscore');
var View = require('../../../core/view');
var Model = require('../../../core/model');
var template = require('./attribution-template.tpl');
var Sanitize = require('../../../core/sanitize');
/**
* Attribution overlay
*
*/
module.exports = View.extend({
className: 'CDB-Attribution',
events: {
'click .js-button': '_toggleAttributions',
'dblclick': 'killEvent'
},
initialize: function () {
this.model = new Model({
visible: false
});
this.map = this.options.map;
this._onDocumentKeyDown = this._onDocumentKeyDown.bind(this);
this._toggleAttributionsBasedOnContainer = this._toggleAttributionsBasedOnContainer.bind(this);
this._initBinds();
},
render: function () {
var attributions = _.compact(this.map.get('attribution')).join(', ');
var isGMaps = this.map.get('provider') !== 'leaflet';
this.$el.html(
template({
attributions: Sanitize.html(attributions)
})
);
this.$el.toggleClass('CDB-Attribution--gmaps', !!isGMaps);
this._toggleAttributionsBasedOnContainer();
return this;
},
_initBinds: function () {
this.model.bind('change:visible', function (mdl, isVisible) {
this[ isVisible ? '_showAttributions' : '_hideAttributions' ]();
}, this);
this.map.bind('change:attribution', this.render, this);
this.add_related_model(this.map);
$(window).bind('resize', this._toggleAttributionsBasedOnContainer);
},
_enableDocumentBinds: function () {
$(document).bind('keydown', this._onDocumentKeyDown);
},
_disableDocumentBinds: function () {
$(document).unbind('keydown', this._onDocumentKeyDown);
},
_onDocumentKeyDown: function (ev) {
if (ev && ev.keyCode === 27) {
this._toggleAttributions();
}
},
_showAttributions: function () {
this.$el.addClass('is-active');
this._enableDocumentBinds();
},
_hideAttributions: function () {
this.$el.removeClass('is-active');
this._disableDocumentBinds();
},
_toggleAttributions: function () {
this.model.set('visible', !this.model.get('visible'));
},
_toggleAttributionsBasedOnContainer: function () {
var MAP_CONTAINER_MOBILE_WIDTH = 650;
if (this.map.getMapViewSize().x > MAP_CONTAINER_MOBILE_WIDTH) {
this.model.set('visible', true);
} else {
this.model.set('visible', false);
}
},
clean: function () {
this._disableDocumentBinds();
$(window).unbind('resize', this._toggleAttributionsBasedOnContainer);
View.prototype.clean.call(this);
}
});
@@ -0,0 +1,25 @@
<div class="CDB-infowindow CDB-infowindow--light js-infowindow">
<div class="CDB-infowindow-close js-close"></div>
<div class="CDB-infowindow-container">
<% if (typeof loading !== 'undefined' && loading) { %>
<div class="CDB-Loader js-loader is-visible"></div>
<% } %>
<div class="CDB-infowindow-bg">
<div class="CDB-infowindow-inner js-inner">
<ul class="CDB-infowindow-list js-content">
<% if (content.fields) { %>
<% _.each(content.fields, function (field) { %>
<li class="CDB-infowindow-listItem">
<% if (field.title) { %><h5 class="CDB-infowindow-subtitle"><%- field.title %></h5><% } %>
<% if (field.value) { %><h4 class="CDB-infowindow-title"><%- field.value %></h4><% } %>
</li>
<% }) %>
<% } %>
</ul>
</div>
</div>
<div class="CDB-hook">
<div class="CDB-hook-inner"></div>
</div>
</div>
</div>
+154
View File
@@ -0,0 +1,154 @@
var _ = require('underscore');
var Backbone = require('backbone');
var defaultInfowindowTemplate = require('./default-infowindow-template.tpl');
var InfowindowModel = Backbone.Model.extend({
defaults: {
offset: [28, 0], // offset of the tip calculated from the bottom left corner
maxHeight: 180, // max height of the content, not the whole infowindow
autoPan: true,
template_type: 'mustache',
content: '',
alternative_names: { },
visibility: false
},
DEFAULT_TEMPLATE: defaultInfowindowTemplate,
TEMPLATE_ATTRIBUTES: ['template', 'template_type', 'alternative_names', 'width', 'maxHeight', 'offset'],
initialize: function (attrs) {
attrs = attrs || {};
this._fields = new Backbone.Collection(attrs.fields || []);
this.unset('fields', { silent: true });
// Set a default template
if (!this._hasTemplate()) {
this.set({
template: this.DEFAULT_TEMPLATE,
template_type: 'underscore'
});
}
},
_hasTemplate: function () {
return this.get('template') && this.get('template').trim && this.get('template').trim();
},
updateContent: function (attributes, options) {
options = options || {};
options = _.pick(options, 'showEmptyFields');
var fields = this._fields.toJSON();
this.set('content', InfowindowModel.contentForFields(attributes, fields, options));
},
setInfowindowTemplate: function (infowindowTemplateModel) {
var attrs = _.pick(infowindowTemplateModel.toJSON(), this.TEMPLATE_ATTRIBUTES);
// Remove keys that have a falsy value
attrs = _.pick(attrs, _.identity);
this.set(_.clone(attrs));
this._fields.reset(infowindowTemplateModel.fields.toJSON());
this._infowindowTemplateModel = infowindowTemplateModel;
},
setLoading: function () {
this.set({
content: {
fields: [{
type: 'loading',
title: 'loading',
value: '…'
}]
}
});
return this;
},
setError: function () {
this.set({
content: {
fields: [{
title: null,
alternative_name: null,
value: 'There has been an error...',
index: null,
type: 'error'
}],
data: {}
}
});
return this;
},
setLatLng: function (latLng) {
this.set('latlng', latLng);
},
show: function () {
this.set('visibility', true);
},
hide: function () {
this.set('visibility', false);
},
isVisible: function () {
return !!this.get('visibility');
},
setCurrentFeatureId: function (featureId) {
this.set('currentFeatureId', featureId);
},
unsetCurrentFeatureId: function () {
this.unset('currentFeatureId');
},
getCurrentFeatureId: function (featureId) {
return this.get('currentFeatureId');
},
getAlternativeName: function (fieldName) {
return this.get('alternative_names') && this.get('alternative_names')[fieldName];
},
hasInfowindowTemplate: function (infowindowTemplateModel) {
return this._infowindowTemplateModel && this._infowindowTemplateModel === infowindowTemplateModel;
}
}, {
contentForFields: function (attributes, fields, options) {
options = options || {};
var renderFields = [];
for (var j = 0; j < fields.length; ++j) {
var field = fields[j];
var value = attributes[field.name];
if (options.showEmptyFields || (value !== undefined && value !== null)) {
renderFields.push({
name: field.name,
title: field.title ? field.name : null,
value: (value !== undefined && value !== null) ? value : null,
index: j
});
}
}
// manage when there is no data to render
if (renderFields.length === 0) {
renderFields.push({
title: null,
value: 'No data available',
index: 0,
type: 'empty'
});
}
return {
fields: renderFields,
data: attributes
};
}
});
module.exports = InfowindowModel;
+676
View File
@@ -0,0 +1,676 @@
var _ = require('underscore');
var $ = require('jquery');
var Ps = require('perfect-scrollbar');
require('clip-path-polygon');
var sanitize = require('../../core/sanitize');
var Template = require('../../core/template');
var View = require('../../core/view');
var util = require('../../core/util');
var ESC_KEY = 27;
/**
* Usage:
* var infowindow = new Infowindow({
* model: infowindowModel,
* mapView: mapView
* });
*
* // Show the infowindow:
* infowindow.showInfowindow();
*/
var Infowindow = View.extend({
options: {
imageTransitionSpeed: 300,
hookMargin: 24,
hookHeight: 16
},
className: 'CDB-infowindow-wrapper',
events: {
// Close bindings
'click .js-close': '_closeInfowindow',
// some migrations doesn't have js-close class
'click .close': '_closeInfowindow',
'touchstart .js-close': '_closeInfowindow',
'MSPointerDown .js-close': '_closeInfowindow',
// Rest infowindow bindings
'dragstart': '_stopPropagation',
'mousedown': '_stopPropagation',
'pointerdown': '_stopPropagation',
'touchstart': '_stopPropagation',
'MSPointerDown': '_stopPropagation',
'dblclick': '_stopPropagation',
'DOMMouseScroll': 'killEvent',
'MozMousePixelScroll': 'killEvent',
'mousewheel': 'killEvent',
'dbclick': '_stopPropagation',
'click': '_stopPropagation'
},
initialize: function () {
this.mapView = this.options.mapView;
this._compileTemplate();
this._initBinds();
// Hide the element
this.$el.hide();
},
/**
* Render infowindow content
*/
render: function () {
this.clearSubViews();
this.$el.empty();
if (this.template) {
// Clone fields and template name
var fields = _.map(this.model.attributes.content.fields, function (field) {
return _.clone(field);
});
var data = this.model.get('content') ? this.model.get('content').data : {};
// Sanitized fields
fields = _.map(fields, this._sanitizeField, this);
// Join plan fields values with content to work with
// custom infowindows and CartoDB infowindows.
var values = {};
_.each(fields, function (pair) {
values[pair.name] = pair.value;
});
var obj = _.extend({
content: {
fields: fields,
data: data
}
}, values);
this.$el.html(
sanitize.html(this.template(obj), this.model.get('sanitizeTemplate'))
);
// Set width and max-height from the model only
// If there is no width set, we don't force our infowindow
if (this.model.get('width')) {
this.$('.js-infowindow').css('width', this.model.get('width') + 'px');
}
if (this.model.get('maxHeight')) {
this._getContent().css('max-height', this.model.get('maxHeight') + 'px');
}
if (this._containsCover()) {
this._loadCover();
}
this._setupClasses();
this._renderScroll();
this._renderShadows();
this._bindScroll();
}
return this;
},
_initBinds: function () {
_.bindAll(this, '_onKeyUp', '_onLoadImageSuccess', '_onLoadImageError');
this.listenTo(this.model, 'change:content change:alternative_names change:width change:maxHeight', this.render, this);
this.listenTo(this.model, 'change:latlng', this._updateAndAdjustPan, this);
this.listenTo(this.model, 'change:visibility', this.toggle, this);
this.listenTo(this.model, 'change:template change:sanitizeTemplate', this._compileTemplate, this);
this.listenTo(this.mapView.map, 'change', this._updatePosition, this);
this.listenTo(this.mapView, 'zoomstart', function () {
this.hide(true);
});
this.listenTo(this.mapView, 'zoomend', function () {
this.show();
});
},
// migration issue: some infowindows doesn't have this selector
_getContent: function () {
var $el = this.$('.js-content');
if ($el.length === 0) {
$el = this.$('.cartodb-popup-content');
}
return $el;
},
_onKeyUp: function (event) {
if (event && event.keyCode === ESC_KEY) {
this._closeInfowindow();
}
},
_setupClasses: function () {
var $infowindow = this.$('.js-infowindow');
var hasHeader = this.$('.js-header').length;
var hasCover = this.$('.js-cover').length;
var hasContent = this._getContent().length;
var hasTitle = this.$('.CDB-infowindow-title').length;
var numberOfFields = this.model.get('content') && this.model.get('content').fields.length;
if (hasCover) {
$infowindow
.addClass('has-header-image')
.toggleClass('no-content', numberOfFields < 3);
}
if (hasHeader) {
$infowindow.addClass('has-header');
}
if (hasContent) {
$infowindow.addClass('has-fields');
}
if (hasTitle) {
$infowindow.addClass('has-title');
}
},
_renderScroll: function () {
if (this._getContent().length === 0) {
return;
}
this._content = this._getContent().get(0);
this.$('.js-infowindow').addClass('has-scroll');
Ps.initialize(this._content, {
wheelSpeed: 1,
wheelPropagation: true,
minScrollbarLength: 20
});
},
_renderShadows: function () {
this.$shadowTop = $('<div>').addClass('CDB-infowindow-canvasShadow CDB-infowindow-canvasShadow--top');
this.$shadowBottom = $('<div>').addClass('CDB-infowindow-canvasShadow CDB-infowindow-canvasShadow--bottom');
var $inner = this.$('.js-inner');
$inner.append(this.$shadowTop);
$inner.append(this.$shadowBottom);
_.defer(function () {
this._showOrHideShadows();
}.bind(this));
},
_bindScroll: function () {
this.$(this._content)
.on('ps-y-reach-start', _.bind(this._onScrollTop, this))
.on('ps-y-reach-end', _.bind(this._onScrollBottom, this))
.on('ps-scroll-y', _.bind(this._onScroll, this));
},
_onScrollTop: function () {
this.$shadowTop.removeClass('is-visible');
},
_onScroll: function () {
this._showOrHideShadows();
},
_showOrHideShadows: function () {
var $el = $(this._content);
if ($el.length) {
var currentPos = $el.scrollTop();
var max = $el.get(0).scrollHeight;
var height = $el.outerHeight();
var maxPos = max - height;
this.$shadowTop.toggleClass('is-visible', currentPos > 0);
this.$shadowBottom.toggleClass('is-visible', currentPos < maxPos);
}
},
_onScrollBottom: function () {
this.$shadowBottom.removeClass('is-visible');
},
_container: function () {
return this.el.querySelector('.js-container');
},
/**
* Compile template of the infowindow
*/
_compileTemplate: function () {
var template = this.model.get('template');
if (typeof (template) !== 'function') {
this.template = new Template({
template: template,
type: this.model.get('template_type') || 'mustache'
}).asFunction();
} else {
this.template = template;
}
this.render();
},
_sanitizeValue: function (key, val) {
if (_.isObject(val)) {
return val;
}
return String(val);
},
_sanitizeField: function (attr) {
// Check null or undefined :| and set both to empty == ''
if (attr.value === null || attr.value === undefined) {
attr.value = '';
}
// Get the alternative name
var alternativeName = this.model.getAlternativeName(attr.name);
attr.title = (attr.title && alternativeName) ? alternativeName : attr.title;
// Save new sanitized value
attr.value = JSON.parse(JSON.stringify(attr.value), this._sanitizeValue);
return attr;
},
/**
* Does header contain cover?
*/
_containsCover: function () {
return !!this.$('.js-infowindow').attr('data-cover');
},
_containsTemplateCover: function () {
return this.$('.js-cover img').length > 0;
},
_getCoverURL: function () {
var content = this.model.get('content');
var imageSRC = this.$('.js-cover img').attr('src');
if (imageSRC) {
return imageSRC;
}
if (content && content.fields && content.fields.length > 0) {
return (content.fields[0].value || '').toString();
}
return false;
},
_imageOnHookNotSupported: function () {
var noCssClipPath = util.ie || util.browser.ie || util.browser.edge;
return noCssClipPath;
},
_loadImageHook: function (imageDimensions, coverDimensions, url) {
var $hook = this.$('.CDB-hook');
if (!$hook) {
return;
}
if (this._imageOnHookNotSupported()) {
$hook.hide();
return;
}
var $hookImage = $('<img />')
.addClass('CDB-hookImage js-hookImage')
.attr('src', url);
$hook.append($hookImage);
$hookImage.css({
marginTop: -(imageDimensions.height - this.options.hookHeight),
width: coverDimensions.width,
display: 'none'
});
$hookImage.load(
function () {
$hook.parent().addClass('has-image');
$hookImage.clipPath(this._getHookPoints(imageDimensions.height - this.options.hookHeight));
$hookImage.show();
}.bind(this)
);
},
_getHookPoints: function (imageHeight) {
return [
[24, imageHeight],
[24, imageHeight + 16],
[48, imageHeight],
[24, imageHeight]
];
},
_loadCoverFromTemplate: function (url) {
this.$('.js-cover img').remove();
this._loadCoverFromUrl(url);
},
_loadCoverFromUrl: function (url) {
var $cover = this.$('.js-cover');
this._startCoverLoader();
var $img = $("<img class='CDB-infowindow-media-item' />");
$cover.append($img);
$img
.load(this._onLoadImageSuccess)
.error(this._onLoadImageError)
.attr('src', url);
},
_onLoadImageError: function () {
this._stopCoverLoader();
this._showInfowindowImageError();
},
_onLoadImageSuccess: function () {
var $cover = this.$('.js-cover');
var $img = this.$('.CDB-infowindow-media-item');
var url = $img.attr('src');
var numFields = this.model.get('content').fields.length;
var imageDimensions = { width: $img.width(), height: $img.height() };
var coverDimensions = { width: $cover.width(), height: $cover.height() };
var styles = this._calcImageStyle(imageDimensions, coverDimensions);
$img.css(styles);
$cover.css({ height: imageDimensions.height - this.options.hookHeight });
this._stopCoverLoader();
$img.fadeIn(150);
if (numFields < 3 && imageDimensions.height >= this.$el.height()) {
this._loadImageHook(imageDimensions, coverDimensions, url);
}
},
_calcImageStyle: function (imageDimensions, coverDimensions) {
var styles = {};
var imageRatio = imageDimensions.height / imageDimensions.width;
var coverRatio = coverDimensions.height / coverDimensions.width;
if (imageDimensions.width > coverDimensions.width && imageDimensions.height > coverDimensions.height) {
if (imageRatio < coverRatio) {
styles = { height: coverDimensions.height };
}
} else {
styles = { width: imageDimensions.width };
}
return styles;
},
_loadCover: function () {
this._renderCoverLoader();
this._startCoverLoader();
var url = this._getCoverURL();
if (this._isLoadingFields()) {
return;
}
if (!this._isValidURL(url)) {
this._stopCoverLoader();
this._showInfowindowImageError();
return;
}
if (this._containsTemplateCover()) {
this._loadCoverFromTemplate(url);
} else {
this._loadCoverFromUrl(url);
}
},
_isLoadingFields: function () {
var content = this.model.get('content');
return !content || (content.fields.length === 1 && content.fields[0].type === 'loading');
},
_renderCoverLoader: function () {
var $loader = $('<div>').addClass('CDB-Loader js-loader');
if (this.$('.js-cover').length > 0) {
this.$('.js-cover').append($loader);
}
},
_startCoverLoader: function () {
this.$('.js-infowindow').addClass('is-loading');
this.$('.js-loader').addClass('is-visible');
},
_clearInfowindowImageError: function () {
this.$('.js-infowindow').removeClass('is-fail');
},
_showInfowindowImageError: function () {
this.$('.js-infowindow').addClass('is-fail');
this.$('.js-cover').append('<p class="CDB-infowindow-fail">Non-valid picture URL</p>');
},
_stopCoverLoader: function () {
this.$('.js-infowindow').removeClass('is-loading');
this.$('.js-loader').removeClass('is-visible');
},
/**
* Return true if the provided URL is valid
*/
_isValidURL: function (url) {
if (url) {
var urlPattern = /^(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-|]*[\w@?^=%&amp;\/~+#-])?$/;
return String(url).match(urlPattern) !== null;
}
return false;
},
/**
* Toggle infowindow visibility
*/
toggle: function () {
if (this.model.get('visibility')) {
this.show(true);
} else {
this.hide();
}
},
_stopPropagation: function (ev) {
ev.stopPropagation();
},
/**
* Close infowindow
*/
_closeInfowindow: function (ev) {
if (ev) {
ev.preventDefault();
ev.stopImmediatePropagation();
}
if (this.model.get('visibility')) {
this.model.set('visibility', false);
}
},
/**
* Show infowindow (update, pan, etc)
*/
show: function (adjustPan) {
$(document)
.off('keyup', this._onKeyUp)
.on('keyup', this._onKeyUp);
if (this.model.get('visibility')) {
this.$el.css({ left: -5000 });
this._update(adjustPan);
}
},
/**
* Get infowindow visibility
*/
isHidden: function () {
return !this.model.get('visibility');
},
/**
* Set infowindow to hidden
*/
hide: function (force) {
$(document).off('keyup', this._onKeyUp);
if (force || !this.model.get('visibility')) this._animateOut();
},
_updateAndAdjustPan: function () {
this._update(true);
},
/**
* Update infowindow
*/
_update: function (adjustPan) {
if (!this.isHidden()) {
var delay = 0;
if (adjustPan) {
delay = this.adjustPan();
}
this._updatePosition();
this._animateIn(delay);
}
},
/**
* Animate infowindow to show up
*/
_animateIn: function (delay) {
if (!util.ie || (util.browser.ie && util.browser.ie.version > 8)) {
this.$el.css({
'marginBottom': '-10px',
'display': 'block',
'visibility': 'visible',
opacity: 0
});
this.$el
.delay(delay)
.animate({
opacity: 1,
marginBottom: 0
}, 300);
} else {
this.$el.show();
}
},
/**
* Animate infowindow to disappear
*/
_animateOut: function () {
if (!util.ie || (util.browser.ie && util.browser.ie.version > 8)) {
var self = this;
this.$el.animate({
marginBottom: '-10px',
opacity: '0',
display: 'block'
}, 180, function () {
self.$el.css({visibility: 'hidden'});
});
} else {
this.$el.hide();
}
},
/**
* Adjust pan to show correctly the infowindow
*/
// TODO: This can be private
adjustPan: function () {
var offset = this.model.get('offset');
if (!this.model.get('autoPan') || this.isHidden()) { return; }
var containerHeight = this.$el.outerHeight(true) + 15; // Adding some more space
var containerWidth = this.$el.width();
var pos = this.mapView.latLngToContainerPoint(this.model.get('latlng'));
var adjustOffset = {x: 0, y: 0};
var size = this.mapView.getSize();
var waitCallback = 0;
if (pos.x - offset[0] < 0) {
adjustOffset.x = pos.x - offset[0] - 10;
}
if (pos.x - offset[0] + containerWidth > size.x) {
adjustOffset.x = pos.x + containerWidth - size.x - offset[0] + 10;
}
if (pos.y - containerHeight < 0) {
adjustOffset.y = pos.y - containerHeight - 10;
}
if (pos.y - containerHeight > size.y) {
adjustOffset.y = pos.y + containerHeight - size.y;
}
if (adjustOffset.x || adjustOffset.y) {
this.mapView.panBy(adjustOffset);
waitCallback = 300;
}
return waitCallback;
},
/**
* Update the position (private)
*/
_updatePosition: function () {
if (this.isHidden()) {
return;
}
var offset = this.model.get('offset');
var pos = this.mapView.latLngToContainerPoint(this.model.get('latlng'));
var left = pos.x - offset[0];
var size = this.mapView.getSize();
var bottom = -1 * (pos.y - offset[1] - size.y);
this.$el.css({ bottom: bottom, left: left });
},
/**
* Set visibility infowindow
*/
showInfowindow: function () {
this.model.set('visibility', true);
}
});
module.exports = Infowindow;
@@ -0,0 +1,85 @@
var $ = require('jquery');
var Backbone = require('backbone');
var util = require('../../../../core/util');
module.exports = Backbone.View.extend({
initialize: function (opts) {
if (!opts.el) { throw new Error('element is mandatory.'); }
if (!opts.imageClass) { throw new Error('Image class is mandatory.'); }
this._color = this.$el.data('color');
this._icon = this.$el.data('icon');
this._imageClass = opts.imageClass;
this._lastImage = {
url: null,
content: null
};
},
_loadImage: function () {
var self = this;
var isSVG = this._isSVG(this._icon);
if (this.$el.length === 0) {
return;
}
if (isSVG) {
this._requestImage(this._icon, function (content) {
var svg = content.cloneNode(true);
var $svg = $(svg);
$svg = $svg.removeAttr('xmlns:a');
$svg.attr('class', self._imageClass + ' js-image');
self.$el.empty().append($svg);
$svg.css('fill', self._color);
$svg.find('g').css('fill', 'inherit');
$svg.find('path').css('fill', 'inherit');
$svg.find('rect').each(function (_, rect) {
var $rect = $(rect);
if ($rect.css('fill') !== 'none') {
$rect.css('fill', 'inherit');
}
});
});
} else {
var $img = $('<img crossorigin="anonymous"/>');
$img.attr('class', self._imageClass + ' js-image');
$img.attr('src', this._icon + '?req=markup');
this.$el.empty().append($img);
}
},
_requestImage: function (url, callback) {
var self = this;
var completeUrl = url + '?req=ajax';
if (this._lastImage.url === completeUrl) {
callback && callback(this._lastImage.content);
} else {
$.ajax(completeUrl)
.done(function (data) {
self._lastImage.url = completeUrl;
var content = self._lastImage.content = data.getElementsByTagName('svg')[0];
callback && callback(content);
})
.fail(function () {
throw new Error("Couldn't get " + completeUrl + ' file.');
});
}
},
_updateImageColor: function (color) {
this.$('.js-image').css('fill', color);
},
_isSVG: function (url) {
if (!url) {
return false;
}
var noQueryString = url.split('?')[0];
return noQueryString && util.endsWith(noQueryString.toUpperCase(), 'SVG');
}
});
+3
View File
@@ -0,0 +1,3 @@
<h3 class="CDB-Text CDB-Size-small u-upperCase u-bSpace u-altTextColor u-ellipsis <% if (error) { %> u-<%- error %>TextColor <% } %>" title="<%- title %>">
<%- title %>
</h3>
+107
View File
@@ -0,0 +1,107 @@
var _ = require('underscore');
var Backbone = require('backbone');
var sanitize = require('../../../../core/sanitize');
var legendTitleTemplate = require('./legend-title.tpl');
var ImageLoaderView = require('./img-loader-view');
var LegendViewBase = Backbone.View.extend({
className: 'CDB-Legend-item u-tSpace-xl',
initialize: function (opts) {
this._placeholderTemplate = opts.placeholderTemplate;
this.model.on('change', this.render, this);
},
render: function () {
this.$el.html(this._generateHTML());
this._loadImages();
this.model.isVisible()
? this.$el.show()
: this.$el.hide();
this._toggleLoadingClass();
return this;
},
_generateHTML: function () {
if (this.model.isSuccess() && this.model.isAvailable()) {
return this._getLegendHTML();
}
var placeholder = this._getPlaceholderHTML();
if (this.model.isLoading()) {
return placeholder;
}
var message = this.model.isError()
? this._generateTitle({ title: 'Something went wrong', error: 'error' })
: this._generateTitle({ title: 'No data available', error: 'alert' });
return [message, placeholder].join('\n');
},
_getLegendHTML: function () {
var html = [];
// Legend title
if (this.model.get('title')) {
html.push(this._generateTitle({ title: this.model.get('title') }));
}
// Pre HTML Snippet
if (this.model.get('preHTMLSnippet')) {
html.push(this._sanitize(this.model.get('preHTMLSnippet')));
}
// Template
html.push(this._sanitize(this._getCompiledTemplate()));
// Post HTML Snippet
if (this.model.get('postHTMLSnippet')) {
html.push(this._sanitize(this.model.get('postHTMLSnippet')));
}
return html.join('\n');
},
_generateTitle: function (data) {
return legendTitleTemplate({ title: data.title, error: data.error });
},
_getPlaceholderHTML: function () {
return (this._placeholderTemplate && this._placeholderTemplate()) || '';
},
_getCompiledTemplate: function () {
throw new Error('Subclasses of LegendViewBase must implement _getCompiledTemplate');
},
_afterRender: function () { },
_sanitize: function (html) {
return sanitize.html(html);
},
_toggleLoadingClass: function () {
this.$el.toggleClass('is-loading', this.model.isLoading());
},
_loadImages: function () {
_.each(this.$('.js-image-container'), function ($el) {
var iconView = new ImageLoaderView({
el: $el,
imageClass: 'Legend-fillImageAsset'
});
iconView._loadImage();
});
}
});
module.exports = LegendViewBase;
@@ -0,0 +1,37 @@
<div class="Bubble-container">
<ul class="Bubble-list<% if (hasCustomLabels && labels[1] === '') { %> Bubble-list--custom<% } %>">
<% for (var i in bubbleSizes) { %>
<%
var customCssClass = '';
var index = +i;
if (hasCustomLabels) {
customCssClass = (labels[0] !== '' && index === 0) ? '' : 'Bubble-item--custom';
}
%>
<li class="js-bubbleItem Bubble-item <%- customCssClass %>">
<% if (!hasCustomLabels || (hasCustomLabels && index === 0)) {%>
<div class="Bubble-label CDB-Text CDB-Size-small" style="height: <%- bubbleSizes[i] %>%;">
<div class="Bubble-numbersItem CDB-Text CDB-Size-small"><%= formatter.formatNumber(labels[i]) %></div>
</div>
<% } %>
<div class="Bubble-circle">
<span class="Bubble-itemCircle" style="height: <%- bubbleSizes[i] %>%; width: <%- bubbleSizes[i] %>%; <%- fillColor ? 'opacity:1; background-color:' + fillColor + ';': ''%>" ></span>
</div>
</li>
<% } %>
<% if (labels[labels.length - 1]) { %>
<li class="js-bubbleItem Bubble-item">
<div class="Bubble-label CDB-Text CDB-Size-small" style="height: 0%;">
<div class="Bubble-numbersItem CDB-Text CDB-Size-small"><%= formatter.formatNumber(labels[labels.length - 1]) %></div>
</div>
</li>
<% } %>
</ul>
<% if (!hasCustomLabels) { %>
<p class="Bubble-average CDB-Text CDB-Size-small u-altTextColor <% if (hasCustomLabels) { %>Bubble-average--custom<% } %>" style="bottom: <%- avgSize %>%;">
AVG: <%= formatter.formatNumber(avgLabel) %>
</p>
<% } %>
</div>
+112
View File
@@ -0,0 +1,112 @@
var _ = require('underscore');
var LegendViewBase = require('../base/legend-view-base');
var template = require('./legend-template.tpl');
var formatter = require('../../../../util/formatter');
var BubbleLegendView = LegendViewBase.extend({
className: 'CDB-Legend-item u-tSpace-xl u-clearfix',
_getCompiledTemplate: function () {
return template({
hasCustomLabels: this._hasCustomLabels(),
labels: this._calculateLabels(),
bubbleSizes: this._calculateBubbleSizes(),
labelPositions: this._calculateLabelPositions(),
avgSize: this._calculateAverageSize(),
avgLabel: this.model.get('avg'),
fillColor: this.model.get('fillColor'),
formatter: formatter
});
},
_hasCustomLabels: function () {
var topLabel = this.model.get('topLabel');
var bottomLabel = this.model.get('bottomLabel');
return ((topLabel != null && topLabel !== '') || (bottomLabel != null && bottomLabel !== ''));
},
_calculateLabelPositions: function () {
var labelPositions;
if (this._hasCustomLabels()) {
labelPositions = [0, 100];
} else {
labelPositions = this._calculateBubbleSizes();
labelPositions.push(0);
}
return labelPositions;
},
_calculateLabels: function () {
var labels;
if (this._hasCustomLabels()) {
labels = [];
labels.push(this.model.get('bottomLabel'));
labels.push(this.model.get('topLabel'));
labels = labels.reverse();
} else {
labels = this.model.get('values').slice(0);
if (this._areSizesInAscendingOrder()) {
labels = labels.reverse();
}
}
return labels;
},
_calculateValues: function () {
var sizes = this.model.get('sizes').slice(0);
if (this._areSizesInAscendingOrder()) {
sizes = sizes.reverse();
}
return sizes;
},
_areSizesInAscendingOrder: function () {
var sizes = this.model.get('sizes').slice(0);
return _.first(sizes) < _.last(sizes);
},
_calculateBubbleSizes: function () {
var sizes = this._calculateValues();
var maxSize = _.max(sizes);
return _.map(sizes, function (size, index) {
return size * 100 / maxSize;
});
},
_calculateAverageSize: function () {
// we build the size range, and reverse it to be able to normalize it with values.
var sizes = this._calculateBubbleSizes().reverse();
// we put 0 as the initial of the range
sizes.unshift(0);
// we need values and sizes because avg is a value and we need to place it taking sizes as reference.
var values = this.model.get('values').slice(0);
var avg = this.model.get('avg');
// we need to position it in the right range
// this reduce thing search the index of the range (index base) that avg belongs to
// while avg is greater than the current value, memo is the next index, if not, memo doesn't change anymore
// index + 1 because we pushed 0 to sizes and we need both arrays to have the same length
var index = _.reduce(values, function (memo, value, index) {
return value > avg ? memo : index + 1;
}, 0);
// avg it's at least equal to the last item
if (index === values.length) {
return 100;
}
// with the index calculate above, we get the % of the sizes we have to place the avg
var minValue = sizes[index - 1];
var maxValue = sizes[index];
// Inside the range, we position it lineal
var offset = (avg - values[index - 1]) / (values[index] - values[index - 1]);
return minValue + (maxValue - minValue) * offset;
}
});
module.exports = BubbleLegendView;
@@ -0,0 +1,25 @@
<div class="Legend-placeholder">
<div class="CDB-Loader is-visible Legend-loading"></div>
<div class="Legend-placeholderInner">
<svg width="191px" height="55px" viewBox="0 3 191 55" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Graph" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 3.000000)">
<g id="Group-2" fill="#F9F9F9">
<g id="Group-3">
<rect id="Rectangle-3147" x="0" y="45" width="72" height="9"></rect>
<rect id="Rectangle-Copy-2" x="0" y="2" width="78" height="9"></rect>
<rect id="Rectangle-Copy-3" x="0" y="24" width="48" height="9"></rect>
<ellipse id="Oval-1585" cx="164" cy="27" rx="27" ry="27"></ellipse>
<ellipse id="Oval-1585" stroke="#FFFFFF" cx="164" cy="33" rx="21" ry="21"></ellipse>
<circle id="Oval-1585" stroke="#FFFFFF" cx="164" cy="45" r="9"></circle>
</g>
</g>
<g id="Group" transform="translate(137.000000, 0.000000)"></g>
</g>
</svg>
</div>
</div>
@@ -0,0 +1,14 @@
<% if (categories && categories.length > 0) { %>
<ul>
<% for(var i in categories) { %>
<li class="Legend-categoryListItem u-flex u-alignCenter">
<% if (categories[i].icon) { %>
<span class="Legend-categoryIcon js-image-container" data-icon="<%= categories[i].icon %>" data-color="<%= categories[i].color %>"></span>
<% } else if (categories[i].color) { %>
<span class="Legend-categoryCircle" style="opacity:1; background: <%= categories[i].color %>;"></span>
<% } %>
<p class="Legend-categoryTitle CDB-Text CDB-Size-small u-upperCase u-ellipsis" title="<%= categories[i].title %>"><%= categories[i].title %></p>
</li>
<% } %>
</ul>
<% }%>
@@ -0,0 +1,25 @@
var LegendViewBase = require('../base/legend-view-base');
var template = require('./legend-template.tpl');
var CategoryLegendView = LegendViewBase.extend({
_getCompiledTemplate: function () {
return template({
categories: this._getCategories()
});
},
_getCategories: function () {
var categories = this.model.get('categories').slice(0);
var _default = this.model.get('default');
if (_default) {
categories.push({
title: 'Others',
icon: _default.icon,
color: _default.color
});
}
return categories;
}
});
module.exports = CategoryLegendView;
@@ -0,0 +1,14 @@
<div class="Legend-placeholder">
<div class="CDB-Loader is-visible Legend-loading"></div>
<div class="Legend-placeholderInner">
<svg width="191px" height="57px" viewBox="0 0 191 57" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-3147" fill="#F9F9F9" x="0" y="48" width="72" height="9"></rect>
<rect id="Rectangle" fill="#F9F9F9" x="0" y="29" width="78" height="9"></rect>
<rect id="Rectangle" fill="#F9F9F9" x="0" y="0" width="117" height="9"></rect>
<rect id="Rectangle" fill="#F9F9F9" x="182" y="29" width="9" height="9" rx="4.5"></rect>
<rect id="Rectangle-Copy" fill="#F9F9F9" x="182" y="48" width="9" height="9" rx="4.5"></rect>
</g>
</svg>
</div>
</div>
@@ -0,0 +1,16 @@
<div class="u-flex u-justifySpace u-bSpace">
<% if (!hasCustomLabels) { %>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= labels.left %> <%- suffix %></p>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= labels.right %> <%- suffix %></p>
<% } else { %>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= labels.left %> <%- suffix %></p>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= labels.right %> <%- suffix %></p>
<% } %>
</div>
<div class="Legend-choropleth <% if (hasCustomLabels) { %>no-average<% } %>" style="opacity:1; background: linear-gradient(90deg <% for(var i in colors) { %>,<%= colors[i].value %><% } %>);">
<% if (!hasCustomLabels) { %>
<span class="Legend-choroplethAverage CDB-Text CDB-Size-small u-altTextColor" style="opacity:1; left: <%- avgPercentage %>%;">
<span class="Legend-choroplethAverageText"><%= formatter.formatNumber(avg) %> AVG</span>
</span>
<% } %>
</div>
@@ -0,0 +1,50 @@
var LegendViewBase = require('../base/legend-view-base');
var template = require('./legend-template.tpl');
var formatter = require('../../../../util/formatter');
var Sanitize = require('../../../../core/sanitize');
var ChoroplethLegendView = LegendViewBase.extend({
_getCompiledTemplate: function () {
return template({
colors: this.model.get('colors'),
avg: this.model.get('avg'),
hasCustomLabels: this._hasCustomLabels(),
avgPercentage: this._calculateAVGPercentage(),
prefix: this.model.get('prefix'),
suffix: this.model.get('suffix'),
labels: this._getLabels(),
formatter: formatter
});
},
_hasCustomLabels: function () {
var leftLabel = this.model.get('leftLabel');
var rightLabel = this.model.get('rightLabel');
return ((leftLabel != null && leftLabel !== '') || (rightLabel != null && rightLabel !== ''));
},
_getLabels: function () {
var colors = this.model.get('colors');
var leftLabel = this.model.get('leftLabel');
var rightLabel = this.model.get('rightLabel');
var o = {};
o.left = Sanitize.html(formatter.formatNumber(colors[0].label));
o.right = Sanitize.html(formatter.formatNumber(colors[colors.length - 1].label));
if (leftLabel != null && leftLabel !== '') {
o.left = leftLabel;
}
if (rightLabel != null && rightLabel !== '') {
o.right = rightLabel;
}
return o;
},
// In order to work with negative values, we need to include the total range
_calculateAVGPercentage: function () {
return (this.model.get('avg') - this.model.get('min')) * 100 / (this.model.get('max') - this.model.get('min'));
}
});
module.exports = ChoroplethLegendView;
@@ -0,0 +1,13 @@
<div class="Legend-placeholder">
<div class="CDB-Loader is-visible Legend-loading"></div>
<div class="Legend-placeholderInner">
<svg width="192px" height="57px" viewBox="0 0 192 57" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Graph" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 48.000000)">
<rect id="Rectangle-3147" fill="#F9F9F9" x="0" y="0" width="192" height="9" rx="4"></rect>
</g>
<rect id="Rectangle" stroke="none" fill="#F9F9F9" fill-rule="evenodd" x="0" y="29" width="18" height="9"></rect>
<rect id="Rectangle" stroke="none" fill="#F9F9F9" fill-rule="evenodd" x="0" y="0" width="117" height="9"></rect>
<rect id="Rectangle" stroke="none" fill="#F9F9F9" fill-rule="evenodd" x="169" y="29" width="23" height="9"></rect>
</svg>
</div>
</div>
@@ -0,0 +1,10 @@
<div class="u-flex u-justifySpace u-bSpace--m">
<% if (hasCustomLabels) { %>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= leftLabel %> <%- suffix %></p>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%= rightLabel %> <%- suffix %></p>
<% } else { %>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%- suffix %></p>
<p class="CDB-Text CDB-Size-small"><%- prefix %> <%- suffix %></p>
<% } %>
</div>
<div class="Legend-choropleth" style="opacity:1; background: linear-gradient(90deg <% for(var i in colors) { %>,<%= colors[i].color %><% } %>);"></div>
@@ -0,0 +1,23 @@
var LegendViewBase = require('../base/legend-view-base');
var template = require('./legend-template.tpl');
var ChoroplethLegendView = LegendViewBase.extend({
_getCompiledTemplate: function () {
return template({
colors: this.model.get('colors'),
prefix: this.model.get('prefix'),
suffix: this.model.get('suffix'),
hasCustomLabels: this._hasCustomLabels(),
leftLabel: this.model.get('leftLabel'),
rightLabel: this.model.get('rightLabel')
});
},
_hasCustomLabels: function () {
var leftLabel = this.model.get('leftLabel');
var rightLabel = this.model.get('rightLabel');
return ((leftLabel != null && leftLabel !== '') || (rightLabel != null && rightLabel !== ''));
}
});
module.exports = ChoroplethLegendView;
@@ -0,0 +1,14 @@
<% if (items && items.length > 0) { %>
<ul>
<% for(var i in items) { %>
<li class="Legend-categoryListItem u-flex u-alignCenter">
<% if (items[i].icon) { %>
<span class="Legend-categoryIcon js-image-container" data-icon="<%= items[i].icon %>" data-color="<%= items[i].color %>"></span>
<% } else if (items[i].color) { %>
<span class="Legend-categoryCircle" style="opacity:1; background: <%= items[i].color %>;"></span>
<% } %>
<p class="Legend-categoryTitle CDB-Text CDB-Size-small u-upperCase u-ellipsis" title="<%= items[i].title %>"><%= items[i].title %></p>
</li>
<% } %>
</ul>
<% }%>
+24
View File
@@ -0,0 +1,24 @@
var LegendViewBase = require('../base/legend-view-base');
var template = require('./legend-template.tpl');
var CustomLegendView = LegendViewBase.extend({
_getCompiledTemplate: function () {
var htmlTemplate = this.model.get('html');
if (htmlTemplate === '') {
htmlTemplate = template({
items: this.model.get('items')
});
} else {
htmlTemplate = this._patchStylesForIE(htmlTemplate);
}
return htmlTemplate;
},
_patchStylesForIE: function (str) {
var find = /\bstyle=(['"])/g;
var replace = 'style="opacity:1; ';
return str.replace(find, replace);
}
});
module.exports = CustomLegendView;
@@ -0,0 +1,17 @@
<h2 class="CDB-Text CDB-Size-medium is-semibold u-flex u-alignCenter">
<% if (showLayerSelector) { %>
<span class="u-iBlock u-rSpace--m">
<% if (isLayerVisible) { %>
<input class="CDB-Checkbox js-toggle-layer" type="checkbox" checked>
<% } else { %>
<input class="CDB-Checkbox js-toggle-layer" type="checkbox">
<% } %>
<span class="u-iBlock CDB-Checkbox-face"></span>
</span>
<% } %>
<span class="u-ellipsis"><%- layerName %></span>
</h2>
<% if (showLegends) { %>
<div class="Legends js-legends"></div>
<% } %>
+123
View File
@@ -0,0 +1,123 @@
var _ = require('underscore');
var View = require('../../../core/view');
var template = require('./layer-legends-template.tpl');
var LegendViewFactory = require('./legend-view-factory');
var LayerLegendsView = View.extend({
className: 'CDB-LayerLegends js-layer-legends',
events: {
'click .js-toggle-layer': '_onToggleLayerCheckboxClicked'
},
initialize: function (options) {
this._legendViews = [];
this.settingsModel = options.settingsModel;
this.model.on('change:visible', this.render, this);
this.model.on('change:layer_name', this.render, this);
this._getLegendModels().forEach(function (legendModel) {
legendModel.on('change:state', _.debounce(this.render, 150), this);
legendModel.on('change:visible', _.debounce(this.render, 150), this);
this.add_related_model(legendModel);
}, this);
this.settingsModel.on('change', this.render, this);
this.add_related_model(this.settingsModel);
},
render: function () {
var showLegends = this._shouldLegendsBeVisible();
var showLayerSelector = this._shouldLayerSelectorBeVisible();
var shouldVisible = this._shouldLayerLegendsBeVisible();
if (shouldVisible) {
this.$el.html(
template({
layerName: this.model.getName(),
isLayerVisible: this._isLayerVisible(),
showLegends: showLegends,
showLayerSelector: showLayerSelector
})
);
this._renderLegends();
} else {
this.$el.html('');
}
this.trigger('render');
return this;
},
isEmpty: function () {
return this.$el.html() === '';
},
_shouldLegendsBeVisible: function () {
var showLegends = this.settingsModel.get('showLegends');
return showLegends && this._isLayerVisible();
},
_shouldLayerLegendsBeVisible: function () {
var showLegends = this.settingsModel.get('showLegends');
var hasLegends = this.model.legends.hasAnyLegend();
return this._shouldLayerSelectorBeVisible() || (this._isLayerVisible() && showLegends && hasLegends);
},
_shouldLayerSelectorBeVisible: function () {
var isLayerSelectorEnabled = this.settingsModel.get('layerSelectorEnabled');
var showLayerSelector = this.settingsModel.get('showLayerSelector');
var shouldVisible = showLayerSelector && isLayerSelectorEnabled;
return shouldVisible;
},
_renderLegends: function () {
_.each(this._getLegendModels(), this._renderLegend, this);
},
_renderLegend: function (legendModel) {
var legendView = LegendViewFactory.createLegendView(legendModel);
this._legendViews.push(legendView);
this._legendsContainer().append(legendView.render().$el);
},
_legendsContainer: function () {
return this.$('.js-legends');
},
_onToggleLayerCheckboxClicked: function (event) {
var isLayerEnabled = event.target.checked;
if (isLayerEnabled) {
this.model.show();
} else {
this.model.hide();
}
},
_getLegendViews: function () {
return this._legendViews || [];
},
_getLegendModels: function () {
return [
this.model.legends.custom,
this.model.legends.choropleth,
this.model.legends.custom_choropleth,
this.model.legends.category,
this.model.legends.bubble,
this.model.legends.torque
];
},
_isLayerVisible: function () {
return this.model.isVisible();
}
});
module.exports = LayerLegendsView;
+36
View File
@@ -0,0 +1,36 @@
var BubbleLegendView = require('./bubble/legend-view');
var CategoryLegendView = require('./categories/legend-view');
var ChoroplethLegendView = require('./choropleth/legend-view');
var CustomLegendView = require('./custom/legend-view');
var CustomChoroplethLegendView = require('./custom-choropleth/legend-view');
var LEGEND_VIEW_CONSTRUCTORS = {
bubble: BubbleLegendView,
category: CategoryLegendView,
choropleth: ChoroplethLegendView,
custom_choropleth: CustomChoroplethLegendView,
custom: CustomLegendView,
torque: CustomLegendView
};
var PLACEHOLDER_TEMPLATES = {
bubble: require('./bubble/placeholder-template.tpl'),
category: require('./categories/placeholder-template.tpl'),
choropleth: require('./choropleth/placeholder-template.tpl')
};
module.exports = {
createLegendView: function (legendModel) {
var legendType = legendModel.get('type');
var LegendViewClass = LEGEND_VIEW_CONSTRUCTORS[legendType];
var placeholderTemplate = PLACEHOLDER_TEMPLATES[legendType];
if (LegendViewClass) {
return new LegendViewClass({
model: legendModel,
placeholderTemplate: placeholderTemplate
});
}
throw new Error('Legends of type "' + legendType + '" are not supported');
}
};
+156
View File
@@ -0,0 +1,156 @@
var $ = require('jquery');
var _ = require('underscore');
var View = require('../../../core/view');
var Ps = require('perfect-scrollbar');
var template = require('./legends-view.tpl');
var LayerLegendsView = require('./layer-legends-view');
var LegendsView = View.extend({
className: 'CDB-Legends-canvas',
initialize: function (options) {
if (!options.layersCollection) throw new Error('layersCollection is required');
if (!options.settingsModel) throw new Error('settingsModel is required');
this._layersCollection = options.layersCollection;
this.settingsModel = options.settingsModel;
this._isRendered = false;
this._initBinds();
this._layerLegendsViews = [];
},
_initBinds: function () {
this.listenTo(this._layersCollection, 'add remove layerMoved', this._onLayersChanged);
this.listenTo(this.settingsModel, 'change', this._onSettingsModelChanged);
},
render: function () {
this.$el.html(template());
var layerModelsWithLegends = this._layersCollection.getLayersWithLegends();
_.each(layerModelsWithLegends.reverse(), this._renderLayerLegends, this);
this._isRendered = true;
this._renderScroll();
this._renderShadows();
this._bindScroll();
this._showOrHide();
return this;
},
_renderScroll: function () {
Ps.initialize(this._container(), {
wheelSpeed: 1,
wheelPropagation: false,
swipePropagation: true,
stopPropagationOnClick: false,
minScrollbarLength: 20,
suppressScrollX: true
});
},
_renderShadows: function () {
this.$shadowTop = $('<div>').addClass('CDB-Legends-canvasShadow CDB-Legends-canvasShadow--top');
this.$shadowBottom = $('<div>').addClass('CDB-Legends-canvasShadow CDB-Legends-canvasShadow--bottom');
this.$el.append(this.$shadowTop);
this.$el.append(this.$shadowBottom);
_.defer(function () {
this._showOrHideShadows();
}.bind(this));
},
_bindScroll: function () {
this.$(this._container())
.on('ps-y-reach-start', _.bind(this._onScrollTop, this))
.on('ps-y-reach-end', _.bind(this._onScrollBottom, this))
.on('ps-scroll-y', _.bind(this._onScroll, this));
},
_onScrollTop: function () {
this.$shadowTop.removeClass('is-visible');
},
_onScroll: function () {
this._showOrHideShadows();
},
_showOrHideShadows: function () {
var $el = $(this._container());
if ($el.length) {
var currentPos = $el.scrollTop();
var max = $el.get(0).scrollHeight;
var height = $el.outerHeight();
var maxPos = max - height;
this.$shadowTop.toggleClass('is-visible', currentPos > 0);
this.$shadowBottom.toggleClass('is-visible', currentPos < maxPos);
}
},
_onScrollBottom: function () {
this.$shadowBottom.removeClass('is-visible');
},
_container: function () {
return this.el.querySelector('.js-container');
},
_renderLayerLegends: function (layerModel) {
var layerLegendsView = new LayerLegendsView({
model: layerModel,
settingsModel: this.settingsModel
});
this._layerLegendsViews.push(layerLegendsView);
layerLegendsView.on('render', this._showOrHide, this);
this.addView(layerLegendsView);
this.$(this._container()).append(layerLegendsView.render().$el);
},
_onLayersChanged: function (layerModel) {
// If view has already been rendered and a layer is added / removed
if (this._isRendered && this._hasLegends(layerModel)) {
this._clear();
this.render();
}
},
_showOrHide: function () {
this.$el.toggle(this._isAnyLayerLegendViewVisible());
},
_isAnyLayerLegendViewVisible: function () {
return _.any(this._layerLegendsViews, function (layerLegendView) {
return !layerLegendView.isEmpty();
});
},
_hasLegends: function (layerModel) {
return !!layerModel.legends;
},
_clear: function () {
_.each(this._layerLegendsViews, function (layerLegendView) {
layerLegendView.clean();
});
this._layerLegendsViews = [];
this.$el.html('');
},
show: function () {
this.$el.show();
},
hide: function () {
this.$el.hide();
},
clean: function () {
View.prototype.clean.call(this);
this._clear();
}
});
module.exports = LegendsView;
+1
View File
@@ -0,0 +1 @@
<div class="CDB-Legends-canvasInner js-container"></div>
+22
View File
@@ -0,0 +1,22 @@
var View = require('../tiles/tiles-view.js');
var template = require('../tiles/tiles-template.tpl');
var Sanitize = require('../../../core/sanitize');
/**
* Limits overlay
*
*/
module.exports = View.extend({
className: 'CDB-Limits',
render: function () {
this.$el.html(
template({
limits: Sanitize.html('Some tiles might not be rendering correctly. <a target="_blank" href="https://carto.com/docs/faqs/carto-engine-usage-limits">Learn More</a>')
})
);
return this;
}
});
+15
View File
@@ -0,0 +1,15 @@
var View = require('../../core/view');
var template = require('./logo.tpl');
var URL = 'https://carto.com';
module.exports = View.extend({
className: 'CDB-Logo',
tagName: 'a',
render: function () {
this.$el.html(template());
this.$el.attr('href', URL);
this.$el.attr('target', '_blank');
return this;
}
});
+8
View File
@@ -0,0 +1,8 @@
<svg width="64px" height="25px" viewBox="0 0 64 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g fill="#AAAAAA">
<circle id="Halo" fill-opacity="0.14" cx="51.6129032" cy="12.3870968" r="12.3870968"></circle>
<path d="M4.12518194,16.503468 C5.88495484,16.503468 6.90196645,15.7473046 7.65615484,14.7202766 L5.98779871,13.5465304 C5.50786065,14.1221175 5.01649548,14.5058422 4.18231742,14.5058422 C3.06246194,14.5058422 2.27399226,13.5803884 2.27399226,12.3953562 L2.27399226,12.3727841 C2.27399226,11.2216099 3.06246194,10.2735841 4.18231742,10.2735841 C4.9479329,10.2735841 5.47357935,10.6460228 5.93066323,11.1990379 L7.59901935,9.92371747 C6.89053935,8.96440565 5.83924645,8.28724435 4.20517161,8.28724435 C1.7826271,8.28724435 0,10.0930078 0,12.3953562 L0,12.4179282 C0,14.7767067 1.83976258,16.503468 4.12518194,16.503468 L4.12518194,16.503468 Z M11.4304378,16.3454637 L13.7501385,16.3454637 L14.3214933,14.923425 L17.4182365,14.923425 L17.9895914,16.3454637 L20.3664275,16.3454637 L16.9611527,8.38881855 L14.8242856,8.38881855 L11.4304378,16.3454637 Z M14.9728378,13.2192358 L15.8755785,10.9846035 L16.766892,13.2192358 L14.9728378,13.2192358 Z M24.6830829,16.3454637 L26.8999397,16.3454637 L26.8999397,13.9528272 L27.88267,13.9528272 L29.4938907,16.3454637 L32.0421333,16.3454637 L30.1338081,13.5916745 C31.1279655,13.1740917 31.77931,12.3727841 31.77931,11.1538938 L31.77931,11.1313218 C31.77931,10.3525863 31.539341,9.75442715 31.07083,9.29170027 C30.5337565,8.76125726 29.6881513,8.44524866 28.465452,8.44524866 L24.6830829,8.44524866 L24.6830829,16.3454637 Z M26.8999397,12.2373519 L26.8999397,10.3300142 L28.3626081,10.3300142 C29.0939423,10.3300142 29.5624533,10.6460228 29.5624533,11.2780401 L29.5624533,11.3006121 C29.5624533,11.8761992 29.1167965,12.2373519 28.3740352,12.2373519 L26.8999397,12.2373519 Z M38.3928119,16.3454637 L38.3928119,10.3638723 L36.0045487,10.3638723 L36.0045487,8.44524866 L43.009359,8.44524866 L43.009359,10.3638723 L40.6096687,10.3638723 L40.6096687,16.3454637 L38.3928119,16.3454637 Z M51.5612903,16.516129 C49.2523838,16.516129 47.3806452,14.6674983 47.3806452,12.3870968 C47.3806452,10.1066952 49.2523838,8.25806452 51.5612903,8.25806452 C53.8701969,8.25806452 55.7419355,10.1066952 55.7419355,12.3870968 C55.7419355,14.6674983 53.8701969,16.516129 51.5612903,16.516129 Z" id="Logo"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+1
View File
@@ -0,0 +1 @@
<div class="CDB-OverlayContainer"></div>
+171
View File
@@ -0,0 +1,171 @@
var _ = require('underscore');
var View = require('../../core/view');
var OverlaysFactory = require('../../vis/overlays-factory');
var overlayContainerTemplate = require('./overlays-container.tpl');
var C = require('../../constants');
var Engine = require('../../engine');
var CONTAINED_OVERLAYS = ['attribution', 'fullscreen', 'tiles', 'limits', 'logo', 'search', 'zoom'];
var OverlaysView = View.extend({
initialize: function (opts) {
if (!opts.overlaysCollection) throw new Error('overlaysCollection is required');
if (!opts.engine) throw new Error('engine is required');
if (!opts.visView) throw new Error('visView is required');
if (!opts.mapModel) throw new Error('mapModel is required');
if (!opts.mapView) throw new Error('mapView is required');
this._overlaysCollection = opts.overlaysCollection;
this._engine = opts.engine;
this._visView = opts.visView;
this._mapModel = opts.mapModel;
this._mapView = opts.mapView;
this._overlayViews = [];
this._overlaysFactory = new OverlaysFactory({
mapModel: this._mapModel,
mapView: this._mapView,
visView: this._visView
});
this._initBinds();
this.$el.append(overlayContainerTemplate());
},
render: function () {
this._clearOverlays();
this._renderOverlays();
return this;
},
_initBinds: function () {
this._engine.on(Engine.Events.RELOAD_STARTED, this._showLoaderOverlay, this);
this._engine.on(Engine.Events.RELOAD_ERROR, this._hideLoaderOverlay, this);
this._engine.on(Engine.Events.RELOAD_SUCCESS, this._hideLoaderOverlay, this);
this.listenTo(this._overlaysCollection, 'add remove change', this.render, this);
this.listenTo(this._mapModel, 'error:limit', this._addLimitsOverlay, this);
this.listenTo(this._mapModel, 'error:tile', this._addTilesOverlay, this);
},
_clearOverlays: function () {
while (this._overlayViews.length !== 0) {
this._overlayViews.pop().clean();
}
},
_renderOverlays: function () {
var overlays = this._overlaysCollection.toJSON();
// Sort the overlays by its internal order
overlays = _.sortBy(overlays, function (overlay) {
return overlay.order === null ? Number.MAX_VALUE : overlay.order;
});
overlays.forEach(function (data) {
this._renderOverlay(data);
}.bind(this));
},
_renderOverlay: function (overlay) {
var overlayView = this._createOverlayView(overlay);
if (!overlayView) return;
overlayView.render();
this._isGlobalOverlay(overlay)
? this.$el.append(overlayView.el)
: this._overlayContainer().append(overlayView.el);
this.addView(overlayView);
this._overlayViews.push(overlayView);
},
_createOverlayView: function (overlay) {
return this._overlaysFactory.create(overlay.type, overlay, {
visView: this._visView,
map: this._mapModel
});
},
_isGlobalOverlay: function (overlay) {
return CONTAINED_OVERLAYS.indexOf(overlay.type) === -1;
},
_overlayContainer: function () {
return this.$('.CDB-OverlayContainer');
},
_showLoaderOverlay: function () {
var loaderOverlay = this._getLoaderOverlay();
if (!loaderOverlay) return;
loaderOverlay.show();
},
_hideLoaderOverlay: function () {
var loaderOverlay = this._getLoaderOverlay();
if (!loaderOverlay) return;
loaderOverlay.hide();
},
_getLoaderOverlay: function () {
return this._getOverlayViewByType('loader');
},
_getOverlayViewByType: function (type) {
return _.find(this._overlayViews, function (overlayView) {
return overlayView.type === type;
});
},
_areLimitsErrorsEnabled: function () {
return this._visView.model.get('showLimitErrors');
},
_addLimitsOverlay: function () {
if (!this._areLimitsErrorsEnabled()) return;
this._removeTilesOverlay();
var limitsOverlay = this._getOverlayViewByType(C.OVERLAY_TYPES.LIMITS);
limitsOverlay || this._overlaysCollection.add({
type: C.OVERLAY_TYPES.LIMITS
});
},
_hasLimitsOverlay: function () {
return !!this._getOverlayViewByType(C.OVERLAY_TYPES.LIMITS);
},
_addTilesOverlay: function () {
if (this._hasLimitsOverlay()) return;
var tilesOverlay = this._getOverlayViewByType(C.OVERLAY_TYPES.TILES);
tilesOverlay || this._overlaysCollection.add({
type: C.OVERLAY_TYPES.TILES
});
},
_removeLimitsOverlay: function () {
var overlay = this._overlaysCollection.findWhere({
type: C.OVERLAY_TYPES.LIMITS
});
this._overlaysCollection.remove(overlay);
},
_removeTilesOverlay: function () {
var overlay = this._overlaysCollection.findWhere({
type: C.OVERLAY_TYPES.TILES
});
this._overlaysCollection.remove(overlay);
},
clean: function () {
View.prototype.clean.apply(this);
this._clearOverlays();
}
});
module.exports = OverlaysView;
+237
View File
@@ -0,0 +1,237 @@
var _ = require('underscore');
var View = require('../../../core/view');
var mapboxGeocoder = require('../../../geo/geocoder/mapbox-geocoder');
var tomtomGeocoder = require('../../../geo/geocoder/tomtom-geocoder');
var InfowindowModel = require('../../../geo/ui/infowindow-model');
var Infowindow = require('../../../geo/ui/infowindow-view');
var Point = require('../../../geo/geometry-models/point.js');
var template = require('./search_template.tpl');
var infowindowTemplate = require('./search_infowindow_template.tpl');
var DEFAULT_GEOCODER = 'tomtom';
var GEOCODERS = {
'tomtom': tomtomGeocoder,
'mapbox': mapboxGeocoder
};
var GEOCODERS_WINDOW_API_KEYS = {
'tomtom': 'tomtomApiKey',
'mapbox': 'mapboxApiKey'
};
/**
* UI component to place the map in the
* location found by the geocoder.
*/
var Search = View.extend({
className: 'CDB-Search CDB-Overlay',
_ZOOM_BY_CATEGORY: {
'address': 18,
'building': 18,
'venue': 18,
'postal-area': 15,
'neighbourhood': 15,
'locality': 12,
'localadmin': 11,
'region': 8,
'county': 8,
'country': 5,
'default': 12
},
events: {
'click .js-toggle': '_onToggleClick',
'submit .js-form': '_onSubmit',
'click': '_stopPropagation',
'dblclick': '_stopPropagation',
'mousedown': '_stopPropagation'
},
options: {
searchPin: true,
infowindowWidth: 186,
infowindowOffset: [30, 30],
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAfCAYAAADXwvzvAAACuklEQVR4Ae3PQ+AsNxzA8e8vo/Xus237vVN9qW3b7qW2bdu2caxt29bu/meSmaTpqW63Pfc7wemTZPh9K/Xv3zhzxIgVrho0aMsLGo2N9o+iuYDwV02E5NJpM7d5fMGC515dMP/7l6dNMc+OGJY9Uq99cVMc33I4LOJXCQBQuXPBglNnDRm0Xa1RAWewP3yL/vJLul99Q/pNm0/b+qsnbLHngXAVgAI4b9KkXWc1m9vV58ykst56lKdMptyokdTKRJUIV1MMTGTgbOTknWABgFo2SSbOjuN9wlgIBrSIJ0yiVG9QUgGxUigRRAlpCQYrBs+A/QClliuXV6ppPVibDPPqi5irL8G+/QY2S3FZhityrLNYBWkAI2G5WTA2nGTthKDTJfP/FH1sCb76nNBa7I8/knba6Eyj8wJjLbk4qlCdAFNClWXKiiL72kGRUkSRhwUuTUm7XTqZ3z3KnMM7QhAFUfiKMZ9OQci+ydFFH32BIsDh8hxjDF2T0y0KtHHUczCg34P3wgesfWhZozstW1R/cJpuohA8dI7cWrSfxqM4gwEOnoJnn4HXBVDHwHnriNr2W3G0I8FEkKufMbjcIw1DC+iCuRw2OBduEYAKDD8drlkGlk6BHwAtIEDioD/QBnsnnHAI7A9YAAAGenwEnPuAd8+DewHcS+CeB3szvL0b7ADE/FWzYf5BCxa9dMvqa7oLll7WbTlsxKkDYRi9dPqhRz743L0PuKtOPMXtutHmm/InKf5Y6Co15Upl8qSCqVajXiEeUTRb6GqNIojoGaLEDwEA6B0KIKL8lH8JBeS/3AgK73qAPfc/tCLiAACUCmyvsJHnphwEAYFStNs/NoHgn2ATWPmlF54b/9GHH/Khn88/+9SywJx/+q0SsKTZbB45d/6CO0aNHnutv3kbYDQg9JAAIRDwF/0EjlkjUi3fkAMAAAAASUVORK5CYII=',
iconAnchor: [7, 31]
},
initialize: function () {
this.map = this.model;
this.mapView = this.options.mapView;
this.template = this.options.template || template;
this._initializeGeocoder();
},
_initializeGeocoder: function () {
const injectedGeocoderConfig = this._getGeocodingInfoFromConfig();
this.geocoderService = this.options.geocoderService || injectedGeocoderConfig.provider || DEFAULT_GEOCODER;
this.geocoder = GEOCODERS[this.geocoderService];
const windowApiKey = GEOCODERS_WINDOW_API_KEYS[this.geocoderService];
this.token = this.options.token || injectedGeocoderConfig.token || window[windowApiKey];
},
render: function () {
this.$el.html(this.template(this.options));
return this;
},
_stopPropagation: function (ev) {
if (ev) {
ev.stopPropagation();
}
},
_onSubmit: function (ev) {
ev.preventDefault();
var address = this.$('.js-textInput').val();
if (!address) {
return;
}
// Remove previous pin without any timeout (0 represents the timeout for
// the animation)
this._destroySearchPin(0);
// TODO: we a Better way to pass api keys
return this.geocoder.geocode(address, this.token).then(this._onResult.bind(this));
},
_onResult: function (places) {
var address = this.$('.js-textInput').val();
if (places && places.length > 0) {
var location = places[0];
var zoom = this._getZoomByCategory(location.type);
this.model.setView(location.center, zoom, { silent: false });
if (this.options.searchPin) {
this._createSearchPin(location.center, address);
}
}
},
_onToggleClick: function () {
this.$('.CDB-Search-inner').toggleClass('is-active');
this.$('.js-textInput').focus();
},
// Getting zoom for each type of location
_getZoomByCategory: function (type) {
if (type && this._ZOOM_BY_CATEGORY[type]) {
return this._ZOOM_BY_CATEGORY[type];
}
return this._ZOOM_BY_CATEGORY['default'];
},
_createSearchPin: function (position, address) {
this._destroySearchPin();
this._createPin(position, address);
this._createInfowindow(position, address);
this._bindEvents();
},
_destroySearchPin: function (timeout) {
this._unbindEvents();
this._destroyPin();
this._destroyInfowindow(timeout);
},
_createInfowindow: function (position, address) {
var infowindowModel = new InfowindowModel({
template: infowindowTemplate,
latlng: position,
width: this.options.infowindowWidth,
offset: this.options.infowindowOffset,
content: {
fields: [{
title: 'address',
value: address
}]
}
});
this._searchInfowindow = new Infowindow({
model: infowindowModel,
mapView: this.mapView
});
this.mapView.$el.append(this._searchInfowindow.el);
infowindowModel.set('visibility', true);
},
_destroyInfowindow: function (timeout) {
if (this._searchInfowindow) {
timeout = !_.isUndefined(timeout) && _.isNumber(timeout) ? timeout : 500;
// Hide it and then destroy it (when animation ends)
this._searchInfowindow.hide(true);
var self = this;
setTimeout(function () {
if (self._searchInfowindow) {
self._searchInfowindow.clean();
delete self._searchInfowindow;
}
}, timeout);
}
},
_createPin: function (position, address) {
this._searchPin = new Point({
latlng: [position[0], position[1]],
iconUrl: this.options.iconUrl,
iconAnchor: this.options.iconAnchor
});
this.map.addGeometry(this._searchPin);
},
_toggleSearchInfowindow: function () {
var infowindowVisibility = this._searchInfowindow.model.get('visibility');
this._searchInfowindow.model.set('visibility', !infowindowVisibility);
},
_destroyPin: function () {
if (this._searchPin) {
this.map.removeGeometry(this._searchPin);
delete this._searchPin;
}
},
_bindEvents: function () {
this._searchPin && this._searchPin.bind('click', this._toggleSearchInfowindow, this);
this.mapView.bind('click', this._destroySearchPin, this);
},
_unbindEvents: function () {
this._searchPin && this._searchPin.unbind('click', this._toggleSearchInfowindow, this);
this.mapView.unbind('click', this._destroySearchPin, this);
},
_getGeocodingInfoFromConfig () {
if (!window.geocoderConfiguration) {
return {};
}
const provider = window.geocoderConfiguration.provider;
let token;
if (provider) {
token = window.geocoderConfiguration[provider] &&
window.geocoderConfiguration[provider].search_bar_api_key;
}
return { provider, token };
},
clean: function () {
this._unbindEvents();
this._destroySearchPin();
View.prototype.clean.call(this);
}
});
module.exports = Search;
@@ -0,0 +1,15 @@
<div class="CDB-infowindow CDB-infowindow--light js-infowindow">
<div class="CDB-infowindow-container">
<div class="CDB-infowindow-inner">
<div class="CDB-infowindow-list ">
<div class="CDB-infowindow-listItem">
<h4 class="CDB-infowindow-title"><%- address %></h4>
</div>
</div>
</div>
<div class="CDB-hook">
<div class="CDB-hook-inner">
</div>
</div>
</div>
</div>
+6
View File
@@ -0,0 +1,6 @@
<form class="js-form CDB-Search-inner">
<button type="button" class="CDB-Shape CDB-Search-actionButton js-toggle">
<span class="CDB-Shape-magnify is-small"></span>
</button>
<input type="text" placeholder="Search Location" class="CDB-Search-text js-textInput" value="" />
</form>
+49
View File
@@ -0,0 +1,49 @@
var View = require('../../core/view');
/**
* Show or hide tiles loader
*
* Usage:
*
* var tiles_loader = new TilesLoader();
* mapWrapper.$el.append(tiles_loader.render().$el);
*/
var TilesLoader = View.extend({
className: 'CDB-Loader',
options: {
animationSpeed: 500
},
initialize: function () {
this.isVisible = 0;
},
render: function () {
return this;
},
show: function (ev) {
if (this.isVisible) {
return;
}
this.$el.addClass('is-visible');
this.isVisible++;
},
hide: function (ev) {
this.isVisible--;
if (this.isVisible > 0) {
return;
}
this.isVisible = 0;
this.$el.removeClass('is-visible');
},
visible: function () {
return this.isVisible > 0;
}
});
module.exports = TilesLoader;
+18
View File
@@ -0,0 +1,18 @@
<div class="CDB-Overlay CDB-Overlay--limits">
<button type="button" class="CDB-Shape CDB-Limits-button js-button">
<svg width="12px" height="12px" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>icon-font_114_Warning</title>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-1" transform="translate(-9001.000000, -11000.000000)" fill-rule="nonzero" fill="#636D72">
<g id="icon-font_114_Warning" transform="translate(9001.000000, 11000.000000)">
<path d="M47.3618699,443.171714 C40.4022689,456.721101 41.0006423,457.65748 56.75075,457.65748 L441.243856,457.65748 C456.985511,457.65748 457.59007,456.716689 450.632737,443.171714 L248.997303,50.6152213 L47.3618699,443.171714 Z M210.115762,31.2931921 C231.589444,-10.5131174 266.489496,-10.3489299 287.878844,31.2931921 L489.747111,424.302981 C511.220794,466.109288 489.302388,500 441.243856,500 L56.75075,500 C8.48945932,500 -13.1418536,465.945101 8.24749422,424.302981 L210.115762,31.2931921 Z M222.781864,372.97244 L222.781864,415.31496 L266.474263,415.31496 L266.474263,372.97244 L222.781864,372.97244 Z M222.781864,203.602361 L222.781864,330.62992 L266.474263,330.62992 L266.474263,203.602361 L222.781864,203.602361 Z" id="Combined-Shape"></path>
</g>
</g>
</g>
</svg>
</button>
<p class="CDB-Limits-text js-text u-ellipsis">
<%= limits %>
</p>
</div>
+92
View File
@@ -0,0 +1,92 @@
var $ = require('jquery');
var View = require('../../../core/view');
var Model = require('../../../core/model');
var template = require('./tiles-template.tpl');
var Sanitize = require('../../../core/sanitize');
var ESC_KEY_CODE = 27;
/**
* Limits overlay
*
*/
module.exports = View.extend({
className: 'CDB-Limits CDB-Limits--short',
events: {
'click .js-button': '_toggleLimits',
'dblclick': 'killEvent'
},
initialize: function () {
this.model = new Model({
visible: false
});
this.map = this.options.map;
this._onDocumentClick = this._onDocumentClick.bind(this);
this._onDocumentKeyDown = this._onDocumentKeyDown.bind(this);
this._initBinds();
},
render: function () {
this.$el.html(
template({
limits: Sanitize.html('Some tiles might not be rendering correctly.')
})
);
return this;
},
_initBinds: function () {
this.model.on('change:visible', this._onVisibleChanged, this);
},
_enableDocumentBinds: function () {
$(document).bind('keydown', this._onDocumentKeyDown);
$(document).bind('click', this._onDocumentClick);
},
_disableDocumentBinds: function () {
$(document).unbind('keydown', this._onDocumentKeyDown);
$(document).unbind('click', this._onDocumentClick);
},
_onDocumentKeyDown: function (event) {
if (event && event.keyCode === ESC_KEY_CODE) {
this._toggleLimits();
}
},
_showLimits: function () {
this.$el.addClass('is-active');
this._enableDocumentBinds();
},
_hideLimits: function () {
this.$el.removeClass('is-active');
this._disableDocumentBinds();
},
_toggleLimits: function () {
this.model.set('visible', !this.model.get('visible'));
},
_onDocumentClick: function (event) {
if (!$(event.target).closest(this.el).length) {
this._toggleLimits();
}
},
_onVisibleChanged: function (_model, isVisible) {
isVisible ? this._showLimits() : this._hideLimits();
},
clean: function () {
this._disableDocumentBinds();
View.prototype.clean.call(this);
}
});
+81
View File
@@ -0,0 +1,81 @@
var _ = require('underscore');
var Model = require('../../core/model');
var InfowindowModel = require('./infowindow-model');
var TooltipModel = Model.extend({
defaults: {
template: '<p>{{text}}</p>',
fields: [],
alternative_names: {},
placement: 'bottom|right',
position: { x: 0, y: 0 },
offset: [0, 0],
blacklisted_columns: ['cartodb_id']
},
updateContent: function (attributes) {
var data = attributes;
var nonValidKeys = ['fields', 'content'];
if (this.get('blacklisted_columns')) {
nonValidKeys = nonValidKeys.concat(this.get('blacklisted_columns'));
}
var c = InfowindowModel.contentForFields(data, this.get('fields'), {
show_empty_fields: false
});
// Remove fields and content from data
// and make them visible for custom templates
data.content = _.omit(data, nonValidKeys);
// loop through content values
data.fields = c.fields;
// alternamte names
var names = this.get('alternative_names');
if (names) {
data.fields.forEach(function (field) {
field.title = names[field.title] || field.title;
});
}
this.set('content', data);
},
setTooltipTemplate: function (tooltipTemplate) {
this.set({
template: tooltipTemplate.get('template'),
fields: tooltipTemplate.fields.toJSON(),
alternative_names: tooltipTemplate.get('alternative_names')
});
},
setPosition: function (position) {
this.set('position', position);
},
show: function () {
this.set('visible', true);
},
hide: function () {
this.set('visible', false);
},
isVisible: function () {
return !!this.get('visible');
},
getVerticalOffset: function () {
var offset = this.get('offset');
return (offset && offset[1]) || 0;
},
getHorizontalOffset: function () {
var offset = this.get('offset');
return (offset && offset[0]) || 0;
}
});
module.exports = TooltipModel;
+149
View File
@@ -0,0 +1,149 @@
var _ = require('underscore');
var sanitize = require('../../core/sanitize');
var Template = require('../../core/template');
var View = require('../../core/view');
var FADE_IN_DURATION = 200;
var FADE_OUT_DURATION = 100;
var FADE_TIMEOUT = 50;
var TooltipView = View.extend({
defaultTemplate: '<p>{{text}}</p>',
className: 'CDB-Tooltip-wrapper',
initialize: function (options) {
if (!options.mapView) {
throw new Error('mapView should be present');
}
this._mapView = options.mapView;
this.showing = false;
this.showhideTimeout = null;
this.model.bind('change:visible', this._showOrHide, this);
this.model.bind('change:position', this._updatePosition, this);
this.model.bind('change:placement', this._updatePosition, this);
this.model.bind('change:content change:alternative_names', this.render, this);
},
template: function (data) {
var compiledTemplate = Template.compile(this.model.get('template'), 'mustache');
return compiledTemplate(data);
},
render: function () {
var content = this.model.get('content');
var sanitizedOutput = sanitize.html(this.template(content));
this.$el.html(sanitizedOutput);
this._updatePosition();
return this;
},
_showOrHide: function () {
if (this.model.isVisible()) {
this._show();
} else {
this._hide();
}
},
_hide: function () {
var self = this;
var fadeOut = function () {
self.$el.fadeOut(FADE_OUT_DURATION);
};
clearTimeout(this.showhideTimeout);
this.showhideTimeout = setTimeout(fadeOut, FADE_TIMEOUT);
},
_show: function () {
this.render();
var self = this;
var fadeIn = function () {
self.$el.fadeIn(FADE_IN_DURATION);
};
clearTimeout(this.showhideTimeout);
this.showhideTimeout = setTimeout(fadeIn, FADE_TIMEOUT);
},
_updatePosition: function () {
var position = this.model.get('position');
var placement = this.model.get('placement');
var height = this.$el.innerHeight();
var width = this.$el.innerWidth();
var mapViewSize = this._mapView.getSize();
var top = 0;
var left = 0;
var modifierClass = 'CDB-Tooltip-wrapper--';
// Remove any position modifier
this._removePositionModifiers();
// Vertically
if (placement.indexOf('top') !== -1) {
top = position.y - height;
} else if (placement.indexOf('middle') !== -1) {
top = position.y - (height / 2);
} else { // bottom
top = position.y;
}
// Fix vertical overflow
if (top < 0) {
top = position.y;
modifierClass += 'top';
} else if (top + height > mapViewSize.y) {
top = position.y - height;
modifierClass += 'bottom';
} else {
modifierClass += 'top';
}
// Horizontally
if (placement.indexOf('left') !== -1) {
left = position.x - width;
} else if (placement.indexOf('center') !== -1) {
left = position.x - (width / 2);
} else { // right
left = position.x;
}
// Fix horizontal overflow
if (left < 0) {
left = position.x;
modifierClass += 'Left';
} else if (left + width > mapViewSize.x) {
left = position.x - width;
modifierClass += 'Right';
} else {
modifierClass += 'Left';
}
// Add offsets
top += this.model.getVerticalOffset();
left += this.model.getHorizontalOffset();
this.$el.css({
top: top,
left: left
}).addClass(modifierClass);
},
_removePositionModifiers: function () {
var positions = [ 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' ];
var positionModifiers = _.map(positions, function (className) {
return this._modifierClassName(className);
}, this);
this.$el.removeClass(positionModifiers.join(' '));
},
_modifierClassName: function (className) {
return this.className + '--' + className;
}
});
module.exports = TooltipView;
+5
View File
@@ -0,0 +1,5 @@
<div class="CDB-Overlay">
<button class="CDB-Zoom-action CDB-Zoom-action--out js-zoomOut"></button>
<div class="CDB-Zoom-info js-zoomInfo">-</div>
<button class="CDB-Zoom-action CDB-Zoom-action--in js-zoomIn"></button>
</div>
+63
View File
@@ -0,0 +1,63 @@
var View = require('../../../core/view');
var template = require('./zoom-template.tpl');
/**
* View to control the zoom of the map.
*
* Usage:
*
* var zoomControl = new Zoom({ model: map, template: "" });
* mapWrapper.$el.append(zoomControl.render().$el);
*
*/
module.exports = View.extend({
className: 'CDB-Zoom',
events: {
'click .js-zoomIn': '_onZoomIn',
'click .js-zoomOut': '_onZoomOut',
'dblclick': 'killEvent'
},
options: {
timeout: 0,
msg: ''
},
initialize: function () {
this.map = this.model;
this.template = this.options.template || template;
this.map.bind('change:zoom change:minZoom change:maxZoom', this._checkZoom, this);
},
render: function () {
this.$el.html(this.template(this.options));
this._checkZoom();
return this;
},
_checkZoom: function () {
var zoom = this.map.get('zoom');
this.$('.js-zoomIn')[ zoom < this.map.get('maxZoom') ? 'removeClass' : 'addClass' ]('is-disabled');
this.$('.js-zoomOut')[ zoom > this.map.get('minZoom') ? 'removeClass' : 'addClass' ]('is-disabled');
this.$('.js-zoomInfo').text(zoom);
},
_onZoomIn: function (ev) {
this.killEvent(ev);
if (this.map.get('maxZoom') > this.map.getZoom()) {
this.map.setZoom(this.map.getZoom() + 1);
}
},
_onZoomOut: function (ev) {
this.killEvent(ev);
if (this.map.get('minZoom') < this.map.getZoom()) {
this.map.setZoom(this.map.getZoom() - 1);
}
}
});