cdb
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
cdb.geo.ui.Annotation = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-overlay overlay-annotation",
|
||||
|
||||
defaults: {
|
||||
minZoom: 0,
|
||||
maxZoom: 40,
|
||||
style: {
|
||||
textAlign: "left",
|
||||
zIndex: 5,
|
||||
color: "#ffffff",
|
||||
fontSize: "13",
|
||||
fontFamilyName: "Helvetica",
|
||||
boxColor: "#333333",
|
||||
boxOpacity: 0.7,
|
||||
boxPadding: 10,
|
||||
lineWidth: 50,
|
||||
lineColor: "#333333"
|
||||
}
|
||||
},
|
||||
|
||||
template: cdb.core.Template.compile(
|
||||
'<div class="content">\
|
||||
<div class="text widget_text">{{{ text }}}</div>\
|
||||
<div class="stick"><div class="ball"></div></div>\
|
||||
</div>',
|
||||
'mustache'
|
||||
),
|
||||
|
||||
events: {
|
||||
"click": "stopPropagation"
|
||||
},
|
||||
|
||||
stopPropagation: function(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
this.template = this.options.template || this.template;
|
||||
this.mapView = this.options.mapView;
|
||||
|
||||
this.mobileEnabled = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
this._cleanStyleProperties(this.options.style);
|
||||
|
||||
_.defaults(this.options.style, this.defaults.style);
|
||||
|
||||
this._setupModels();
|
||||
|
||||
this._bindMap();
|
||||
|
||||
},
|
||||
|
||||
_setupModels: function() {
|
||||
|
||||
this.model = new cdb.core.Model({
|
||||
display: true,
|
||||
hidden: false,
|
||||
text: this.options.text,
|
||||
latlng: this.options.latlng,
|
||||
minZoom: this.options.minZoom || this.defaults.minZoom,
|
||||
maxZoom: this.options.maxZoom || this.defaults.maxZoom
|
||||
});
|
||||
|
||||
this.model.on("change:display", this._onChangeDisplay, this);
|
||||
this.model.on("change:text", this._onChangeText, this);
|
||||
this.model.on('change:latlng', this._place, this);
|
||||
|
||||
this.model.on('change:minZoom', this._applyZoomLevelStyle, this);
|
||||
this.model.on('change:maxZoom', this._applyZoomLevelStyle, this);
|
||||
|
||||
this.style = new cdb.core.Model(this.options.style);
|
||||
|
||||
this.style.on("change", this._applyStyle, this);
|
||||
|
||||
this.add_related_model(this.style);
|
||||
|
||||
},
|
||||
|
||||
_bindMap: function() {
|
||||
|
||||
this.mapView.map.bind('change', this._place, this);
|
||||
this.mapView.map.bind('change:zoom', this._applyZoomLevelStyle, this);
|
||||
this.mapView.bind('zoomstart', this.hide, this);
|
||||
this.mapView.bind('zoomend', this.show, this);
|
||||
|
||||
},
|
||||
|
||||
_unbindMap: function() {
|
||||
|
||||
this.mapView.map.unbind('change', this._place, this);
|
||||
this.mapView.map.unbind('change:zoom', this._applyZoomLevelStyle, this);
|
||||
this.mapView.unbind('zoomstart', this.hide, this);
|
||||
this.mapView.unbind('zoomend', this.show, this);
|
||||
|
||||
},
|
||||
|
||||
_onChangeDisplay: function() {
|
||||
|
||||
if (this.model.get("display")) this.show();
|
||||
else this.hide();
|
||||
|
||||
},
|
||||
|
||||
_onChangeText: function() {
|
||||
this.$el.find(".text").html(this._sanitizedText());
|
||||
},
|
||||
|
||||
_sanitizedText: function() {
|
||||
return cdb.core.sanitize.html(this.model.get("text"), this.model.get('sanitizeText'));
|
||||
},
|
||||
|
||||
_getStandardPropertyName: function(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parts = name.split("-");
|
||||
|
||||
if (parts.length === 1) {
|
||||
return name;
|
||||
} else {
|
||||
return parts[0] + _.map(parts.slice(1), function(l) {
|
||||
return l.slice(0,1).toUpperCase() + l.slice(1);
|
||||
}).join("");
|
||||
}
|
||||
},
|
||||
|
||||
_cleanStyleProperties: function(hash) {
|
||||
|
||||
var standardProperties = {};
|
||||
|
||||
_.each(hash, function(value, key) {
|
||||
standardProperties[this._getStandardPropertyName(key)] = value;
|
||||
}, this);
|
||||
|
||||
this.options.style = standardProperties;
|
||||
|
||||
},
|
||||
|
||||
_belongsToCanvas: function() {
|
||||
|
||||
var mobile = (this.options.device === "mobile") ? true : false;
|
||||
return mobile === this.mobileEnabled;
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
|
||||
if (this.model.get("hidden") || !this._belongsToCanvas()) return;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.$el.css({ opacity: 0, display: "inline-table" }); // makes the element to behave fine in the borders of the screen
|
||||
this.$el.stop().animate({ opacity: 1 }, { duration: 150, complete: function() {
|
||||
callback && callback();
|
||||
}});
|
||||
|
||||
},
|
||||
|
||||
hide: function(callback) {
|
||||
this.$el.stop().fadeOut(150, function() {
|
||||
callback && callback();
|
||||
});
|
||||
},
|
||||
|
||||
_place: function() {
|
||||
|
||||
var latlng = this.model.get("latlng");
|
||||
|
||||
var lineWidth = this.style.get("lineWidth");
|
||||
var textAlign = this.style.get("textAlign");
|
||||
|
||||
var pos = this.mapView.latLonToPixel(latlng);
|
||||
|
||||
if (pos) {
|
||||
|
||||
var top = pos.y - this.$el.height()/2;
|
||||
var left = pos.x + lineWidth;
|
||||
|
||||
if (textAlign === "right") {
|
||||
left = pos.x - this.$el.width() - lineWidth - this.$el.find(".ball").width();
|
||||
}
|
||||
|
||||
this.$el.css({ top: top, left: left });
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
setMinZoom: function(zoom) {
|
||||
|
||||
this.model.set("minZoom", zoom);
|
||||
|
||||
},
|
||||
|
||||
setMaxZoom: function(zoom) {
|
||||
|
||||
this.model.set("maxZoom", zoom);
|
||||
|
||||
},
|
||||
|
||||
setPosition: function(latlng) {
|
||||
|
||||
this.model.set("latlng", latlng);
|
||||
|
||||
},
|
||||
|
||||
setText: function(text) {
|
||||
|
||||
this.model.set("text", text);
|
||||
|
||||
},
|
||||
|
||||
setStyle: function(property, value) {
|
||||
|
||||
var standardProperty = this._getStandardPropertyName(property);
|
||||
|
||||
if (standardProperty) {
|
||||
this.style.set(standardProperty, value);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_applyStyle: function() {
|
||||
|
||||
var textColor = this.style.get("color");
|
||||
var textAlign = this.style.get("textAlign");
|
||||
var boxColor = this.style.get("boxColor");
|
||||
var boxOpacity = this.style.get("boxOpacity");
|
||||
var boxPadding = this.style.get("boxPadding");
|
||||
var lineWidth = this.style.get("lineWidth");
|
||||
var lineColor = this.style.get("lineColor");
|
||||
var fontFamily = this.style.get("fontFamilyName");
|
||||
|
||||
this.$text = this.$el.find(".text");
|
||||
|
||||
this.$text.css({ color: textColor, textAlign: textAlign });
|
||||
|
||||
this.$el.find(".content").css("padding", boxPadding);
|
||||
this.$text.css("font-size", this.style.get("fontSize") + "px");
|
||||
this.$el.css("z-index", this.style.get("zIndex"));
|
||||
|
||||
this.$el.find(".stick").css({ width: lineWidth, left: -lineWidth });
|
||||
|
||||
var fontFamilyClass = "";
|
||||
|
||||
if (fontFamily == "Droid Sans") fontFamilyClass = "droid";
|
||||
else if (fontFamily == "Vollkorn") fontFamilyClass = "vollkorn";
|
||||
else if (fontFamily == "Open Sans") fontFamilyClass = "open_sans";
|
||||
else if (fontFamily == "Roboto") fontFamilyClass = "roboto";
|
||||
else if (fontFamily == "Lato") fontFamilyClass = "lato";
|
||||
else if (fontFamily == "Graduate") fontFamilyClass = "graduate";
|
||||
else if (fontFamily == "Gravitas One") fontFamilyClass = "gravitas_one";
|
||||
else if (fontFamily == "Old Standard TT") fontFamilyClass = "old_standard_tt";
|
||||
|
||||
this.$el
|
||||
.removeClass("droid")
|
||||
.removeClass("vollkorn")
|
||||
.removeClass("roboto")
|
||||
.removeClass("open_sans")
|
||||
.removeClass("lato")
|
||||
.removeClass("graduate")
|
||||
.removeClass("gravitas_one")
|
||||
.removeClass("old_standard_tt");
|
||||
|
||||
this.$el.addClass(fontFamilyClass);
|
||||
|
||||
if (textAlign === "right") {
|
||||
this.$el.addClass("align-right");
|
||||
this.$el.find(".stick").css({ left: "auto", right: -lineWidth });
|
||||
} else {
|
||||
this.$el.removeClass("align-right");
|
||||
}
|
||||
|
||||
this._place();
|
||||
this._applyZoomLevelStyle();
|
||||
|
||||
},
|
||||
|
||||
_getRGBA: function(color, opacity) {
|
||||
return 'rgba(' + parseInt(color.slice(-6,-4),16)
|
||||
+ ',' + parseInt(color.slice(-4,-2),16)
|
||||
+ ',' + parseInt(color.slice(-2),16)
|
||||
+ ',' + opacity + ' )';
|
||||
},
|
||||
|
||||
_applyZoomLevelStyle: function() {
|
||||
|
||||
var boxColor = this.style.get("boxColor");
|
||||
var boxOpacity = this.style.get("boxOpacity");
|
||||
var lineColor = this.style.get("lineColor");
|
||||
|
||||
var minZoom = this.model.get("minZoom");
|
||||
var maxZoom = this.model.get("maxZoom");
|
||||
|
||||
var currentZoom = this.mapView.map.get("zoom");
|
||||
|
||||
if (currentZoom >= minZoom && currentZoom <= maxZoom) {
|
||||
|
||||
var rgbaLineCol = this._getRGBA(lineColor, 1);
|
||||
var rgbaBoxCol = this._getRGBA(boxColor, boxOpacity);
|
||||
|
||||
this.$el.find(".text").animate({ opacity: 1 }, 150);
|
||||
|
||||
this.$el.css("background-color", rgbaBoxCol);
|
||||
|
||||
this.$el.find(".stick").css("background-color", rgbaLineCol);
|
||||
this.$el.find(".ball").css("background-color", rgbaLineCol);
|
||||
|
||||
this.model.set("hidden", false);
|
||||
this.model.set("display", true);
|
||||
|
||||
} else {
|
||||
this.model.set("hidden", true);
|
||||
this.model.set("display", false);
|
||||
}
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
this._unbindMap();
|
||||
cdb.core.View.prototype.clean.call(this);
|
||||
},
|
||||
|
||||
_fixLinks: function() {
|
||||
|
||||
this.$el.find("a").each(function(i, link) {
|
||||
$(this).attr("target", "_top");
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var d = _.clone(this.model.attributes);
|
||||
d.text = this._sanitizedText();
|
||||
this.$el.html(this.template(d));
|
||||
|
||||
this._fixLinks();
|
||||
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self._applyStyle();
|
||||
self._applyZoomLevelStyle();
|
||||
self.show();
|
||||
}, 500);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* FullScreen widget:
|
||||
*
|
||||
* var widget = new cdb.ui.common.FullScreen({
|
||||
* doc: ".container", // optional; if not specified, we do the fullscreen of the whole window
|
||||
* template: this.getTemplate("table/views/fullscreen")
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
cdb.ui.common.FullScreen = cdb.core.View.extend({
|
||||
|
||||
tagName: 'div',
|
||||
className: 'cartodb-fullscreen',
|
||||
|
||||
events: {
|
||||
"click a": "_toggleFullScreen"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
_.bindAll(this, 'render');
|
||||
_.defaults(this.options, this.default_options);
|
||||
this._addWheelEvent();
|
||||
},
|
||||
|
||||
_addWheelEvent: function() {
|
||||
var self = this;
|
||||
var mapView = this.options.mapView;
|
||||
|
||||
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {
|
||||
if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
|
||||
if (self.model.get("allowWheelOnFullscreen")) {
|
||||
mapView.options.map.set("scrollwheel", false);
|
||||
}
|
||||
}
|
||||
mapView.invalidateSize();
|
||||
});
|
||||
},
|
||||
|
||||
_toggleFullScreen: function(ev) {
|
||||
if (ev) {
|
||||
this.killEvent(ev);
|
||||
}
|
||||
|
||||
var doc = window.document;
|
||||
var docEl = doc.documentElement;
|
||||
|
||||
if (this.options.doc) { // we use a custom element
|
||||
docEl = $(this.options.doc)[0];
|
||||
}
|
||||
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||
var mapView = this.options.mapView;
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||
if (docEl.webkitRequestFullScreen) {
|
||||
// Cartodb.js #361 :: Full screen button not working on Safari 8.0.3 #361
|
||||
// Safari has a bug that fullScreen doestn't work with Element.ALLOW_KEYBOARD_INPUT);
|
||||
// Reference: Ehttp://stackoverflow.com/questions/8427413/webkitrequestfullscreen-fails-when-passing-element-allow-keyboard-input-in-safar
|
||||
requestFullScreen.call(docEl, undefined);
|
||||
} else {
|
||||
// CartoDB.js #412 :: Fullscreen button is throwing errors
|
||||
// Nowadays (2015/03/25), fullscreen is not supported in iOS Safari. Reference: http://caniuse.com/#feat=fullscreen
|
||||
if (requestFullScreen) {
|
||||
requestFullScreen.call(docEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (mapView && this.model.get("allowWheelOnFullscreen")) {
|
||||
mapView.map.set("scrollwheel", true);
|
||||
}
|
||||
} else {
|
||||
cancelFullScreen.call(doc);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var options = _.extend(
|
||||
this.options,
|
||||
{
|
||||
mapUrl: location.href || ''
|
||||
}
|
||||
);
|
||||
this.$el.html(this.options.template(options));
|
||||
|
||||
if (!this._canFullScreenBeEnabled()) {
|
||||
this.undelegateEvents();
|
||||
cdb.log.info('FullScreen API is deprecated on insecure origins. See https://goo.gl/rStTGz for more details.');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_canFullScreenBeEnabled: function() {
|
||||
if (this._isInIframe()) {
|
||||
var parentUrl = document.referrer;
|
||||
if (parentUrl.search('https:') !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_isInIframe: function() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
cdb.geo.ui.Header = cdb.core.View.extend({
|
||||
|
||||
className: 'cartodb-header',
|
||||
|
||||
initialize: function() {
|
||||
var extra = this.model.get("extra");
|
||||
|
||||
this.model.set({
|
||||
title: extra.title,
|
||||
description: extra.description,
|
||||
show_title: extra.show_title,
|
||||
show_description: extra.show_description
|
||||
}, { silent: true });
|
||||
},
|
||||
|
||||
show: function() {
|
||||
//var display = this.model.get("display");
|
||||
var hasTitle = this.model.get("title") && this.model.get("show_title");
|
||||
var hasDescription = this.model.get("description") && this.model.get("show_description");
|
||||
|
||||
if (hasTitle || hasDescription) {
|
||||
this.$el.show();
|
||||
if (hasTitle) this.$el.find(".content div.title").show();
|
||||
if (hasDescription) this.$el.find(".content div.description").show();
|
||||
}
|
||||
},
|
||||
|
||||
// Add target attribute to all links
|
||||
_setLinksTarget: function(str) {
|
||||
if (!str) return str;
|
||||
var reg = new RegExp(/<(a)([^>]+)>/g);
|
||||
return str.replace(reg, "<$1 target=\"_blank\"$2>");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = _.clone(this.model.attributes);
|
||||
data.title = cdb.core.sanitize.html(data.title);
|
||||
data.description = this._setLinksTarget(cdb.core.sanitize.html(data.description));
|
||||
this.$el.html(this.options.template(data));
|
||||
|
||||
if (this.options.slides) {
|
||||
this.slides_controller = new cdb.geo.ui.SlidesController({
|
||||
transitions: this.options.transitions,
|
||||
slides: this.options.slides
|
||||
});
|
||||
|
||||
this.$el.append(this.slides_controller.render().$el);
|
||||
}
|
||||
|
||||
if (this.model.get("show_title") || this.model.get("show_description")) {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
cdb.geo.ui.Image = cdb.geo.ui.Text.extend({
|
||||
|
||||
className: "cartodb-overlay image-overlay",
|
||||
|
||||
events: {
|
||||
"click": "stopPropagation"
|
||||
},
|
||||
|
||||
default_options: { },
|
||||
|
||||
stopPropagation: function(e) {
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.template = this.options.template;
|
||||
|
||||
var self = this;
|
||||
|
||||
$(window).on("map_resized", function() {
|
||||
self._place();
|
||||
});
|
||||
|
||||
$(window).on("resize", function() {
|
||||
self._place();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
_applyStyle: function() {
|
||||
|
||||
var style = this.model.get("style");
|
||||
|
||||
var boxColor = style["box-color"];
|
||||
var boxOpacity = style["box-opacity"];
|
||||
var boxWidth = style["box-width"];
|
||||
|
||||
this.$el.find(".text").css(style);
|
||||
this.$el.css("z-index", style["z-index"]);
|
||||
|
||||
var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
|
||||
+ ',' + parseInt(boxColor.slice(-4,-2),16)
|
||||
+ ',' + parseInt(boxColor.slice(-2),16)
|
||||
+', ' + boxOpacity + ' )';
|
||||
|
||||
this.$el.css({
|
||||
backgroundColor: rgbaCol
|
||||
});
|
||||
|
||||
this.$el.find("img").css({ width: boxWidth });
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var content;
|
||||
if (this.model.get("extra").has_default_image) {
|
||||
content = _.template('<img src="<%- url %>" />')({ url: this.model.get("extra").public_default_image_url });
|
||||
} else {
|
||||
content = cdb.core.sanitize.html(this.model.get("extra").rendered_text, this.model.get('sanitizeContent'));
|
||||
}
|
||||
|
||||
var data = _.chain(this.model.attributes).clone().extend({ content: content }).value();
|
||||
this.$el.html(this.template(data));
|
||||
|
||||
var self = this;
|
||||
|
||||
setTimeout(function() {
|
||||
self._applyStyle();
|
||||
self._place();
|
||||
self.show();
|
||||
}, 900);
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
cdb.geo.ui.InfoBox = cdb.core.View.extend({
|
||||
|
||||
className: 'cartodb-infobox',
|
||||
defaults: {
|
||||
pos_margin: 20,
|
||||
position: 'bottom|right',
|
||||
width: 200
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var self = this;
|
||||
_.defaults(this.options, this.defaults);
|
||||
if(this.options.layer) {
|
||||
this.enable();
|
||||
}
|
||||
this.setTemplate(this.options.template || this.defaultTemplate, 'mustache');
|
||||
},
|
||||
|
||||
setTemplate: function(tmpl) {
|
||||
this.template = cdb.core.Template.compile(tmpl, 'mustache');
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
if(this.options.layer) {
|
||||
this.options.layer
|
||||
.on('featureOver', function(e, latlng, pos, data) {
|
||||
this.render(data).show();
|
||||
}, this)
|
||||
.on('featureOut', function() {
|
||||
this.hide();
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if(this.options.layer) {
|
||||
this.options.layer.off(null, null, this);
|
||||
}
|
||||
},
|
||||
|
||||
// set position based on a string like "top|right", "top|left", "bottom|righ"...
|
||||
setPosition: function(pos) {
|
||||
var props = {};
|
||||
if(pos.indexOf('top') !== -1) {
|
||||
props.top = this.options.pos_margin;
|
||||
} else if(pos.indexOf('bottom') !== -1) {
|
||||
props.bottom = this.options.pos_margin;
|
||||
}
|
||||
|
||||
if(pos.indexOf('left') !== -1) {
|
||||
props.left = this.options.pos_margin;
|
||||
} else if(pos.indexOf('right') !== -1) {
|
||||
props.right = this.options.pos_margin;
|
||||
}
|
||||
this.$el.css(props);
|
||||
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
this.$el.html( this.template(data) );
|
||||
if(this.options.width) {
|
||||
this.$el.css('width', this.options.width);
|
||||
}
|
||||
if(this.options.position) {
|
||||
this.setPosition(this.options.position);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,866 @@
|
||||
/** Usage:
|
||||
*
|
||||
* Add Infowindow model:
|
||||
*
|
||||
* var infowindowModel = new cdb.geo.ui.InfowindowModel({
|
||||
* template_name: 'infowindow_light',
|
||||
* latlng: [72, -45],
|
||||
* offset: [100, 10]
|
||||
* });
|
||||
*
|
||||
* var infowindow = new cdb.geo.ui.Infowindow({
|
||||
* model: infowindowModel,
|
||||
* mapView: mapView
|
||||
* });
|
||||
*
|
||||
* Show the infowindow:
|
||||
* infowindow.showInfowindow();
|
||||
*
|
||||
*/
|
||||
|
||||
cdb.geo.ui.InfowindowModel = Backbone.Model.extend({
|
||||
|
||||
SYSTEM_COLUMNS: ['the_geom', 'the_geom_webmercator', 'created_at', 'updated_at', 'cartodb_id', 'cartodb_georef_status'],
|
||||
|
||||
defaults: {
|
||||
template_name: 'infowindow_light',
|
||||
latlng: [0, 0],
|
||||
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: "",
|
||||
content: "",
|
||||
visibility: false,
|
||||
alternative_names: { },
|
||||
fields: null // contains the fields displayed in the infowindow
|
||||
},
|
||||
|
||||
clearFields: function() {
|
||||
this.set({fields: []});
|
||||
},
|
||||
|
||||
saveFields: function(where) {
|
||||
where = where || 'old_fields';
|
||||
this.set(where, _.clone(this.get('fields')));
|
||||
},
|
||||
|
||||
fieldCount: function() {
|
||||
var fields = this.get('fields')
|
||||
if (!fields) return 0;
|
||||
return fields.length
|
||||
},
|
||||
|
||||
restoreFields: function(whiteList, from) {
|
||||
from = from || 'old_fields';
|
||||
var fields = this.get(from);
|
||||
if(whiteList) {
|
||||
fields = fields.filter(function(f) {
|
||||
return _.contains(whiteList, f.name);
|
||||
});
|
||||
}
|
||||
if(fields && fields.length) {
|
||||
this._setFields(fields);
|
||||
}
|
||||
this.unset(from);
|
||||
},
|
||||
|
||||
_cloneFields: function() {
|
||||
return _(this.get('fields')).map(function(v) {
|
||||
return _.clone(v);
|
||||
});
|
||||
},
|
||||
|
||||
_setFields: function(f) {
|
||||
f.sort(function(a, b) { return a.position - b.position; });
|
||||
this.set({'fields': f});
|
||||
},
|
||||
|
||||
sortFields: function() {
|
||||
this.get('fields').sort(function(a, b) { return a.position - b.position; });
|
||||
},
|
||||
|
||||
_addField: function(fieldName, at) {
|
||||
var dfd = $.Deferred();
|
||||
if(!this.containsField(fieldName)) {
|
||||
var fields = this.get('fields');
|
||||
if(fields) {
|
||||
at = at === undefined ? fields.length: at;
|
||||
fields.push({ name: fieldName, title: true, position: at });
|
||||
} else {
|
||||
at = at === undefined ? 0 : at;
|
||||
this.set('fields', [{ name: fieldName, title: true, position: at }], { silent: true});
|
||||
}
|
||||
}
|
||||
dfd.resolve();
|
||||
return dfd.promise();
|
||||
},
|
||||
|
||||
addField: function(fieldName, at) {
|
||||
var self = this;
|
||||
$.when(this._addField(fieldName, at)).then(function() {
|
||||
self.sortFields();
|
||||
self.trigger('change:fields');
|
||||
self.trigger('add:fields');
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
getFieldProperty: function(fieldName, k) {
|
||||
if(this.containsField(fieldName)) {
|
||||
var fields = this.get('fields') || [];
|
||||
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
|
||||
return fields[idx][k];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
setFieldProperty: function(fieldName, k, v) {
|
||||
if(this.containsField(fieldName)) {
|
||||
var fields = this._cloneFields() || [];
|
||||
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
|
||||
fields[idx][k] = v;
|
||||
this._setFields(fields);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
getAlternativeName: function(fieldName) {
|
||||
return this.get("alternative_names") && this.get("alternative_names")[fieldName];
|
||||
},
|
||||
|
||||
setAlternativeName: function(fieldName, alternativeName) {
|
||||
var alternativeNames = this.get("alternative_names") || [];
|
||||
|
||||
alternativeNames[fieldName] = alternativeName;
|
||||
this.set({ 'alternative_names': alternativeNames });
|
||||
this.trigger('change:alternative_names');
|
||||
},
|
||||
|
||||
getFieldPos: function(fieldName) {
|
||||
var p = this.getFieldProperty(fieldName, 'position');
|
||||
if(p == undefined) {
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
return p;
|
||||
},
|
||||
|
||||
containsAlternativeName: function(fieldName) {
|
||||
var names = this.get('alternative_names') || [];
|
||||
return names[fieldName];
|
||||
},
|
||||
|
||||
containsField: function(fieldName) {
|
||||
var fields = this.get('fields') || [];
|
||||
return _.contains(_(fields).pluck('name'), fieldName);
|
||||
},
|
||||
|
||||
removeField: function(fieldName) {
|
||||
if(this.containsField(fieldName)) {
|
||||
var fields = this._cloneFields() || [];
|
||||
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
|
||||
if(idx >= 0) {
|
||||
fields.splice(idx, 1);
|
||||
}
|
||||
this._setFields(fields);
|
||||
this.trigger('remove:fields')
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// updates content with attributes
|
||||
updateContent: function(attributes) {
|
||||
var fields = this.get('fields');
|
||||
this.set('content', cdb.geo.ui.InfowindowModel.contentForFields(attributes, fields));
|
||||
},
|
||||
|
||||
closeInfowindow: function(){
|
||||
if (this.get('visibility')) {
|
||||
this.set("visibility", false);
|
||||
this.trigger('close');
|
||||
}
|
||||
}
|
||||
|
||||
}, {
|
||||
contentForFields: function(attributes, fields, options) {
|
||||
options = options || {};
|
||||
var render_fields = [];
|
||||
for(var j = 0; j < fields.length; ++j) {
|
||||
var field = fields[j];
|
||||
var value = attributes[field.name];
|
||||
if(options.empty_fields || (value !== undefined && value !== null)) {
|
||||
render_fields.push({
|
||||
title: field.title ? field.name : null,
|
||||
value: attributes[field.name],
|
||||
index: j
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// manage when there is no data to render
|
||||
if (render_fields.length === 0) {
|
||||
render_fields.push({
|
||||
title: null,
|
||||
value: 'No data available',
|
||||
index: 0,
|
||||
type: 'empty'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
fields: render_fields,
|
||||
data: attributes
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
cdb.geo.ui.Infowindow = cdb.core.View.extend({
|
||||
className: "cartodb-infowindow",
|
||||
|
||||
spin_options: {
|
||||
lines: 10, length: 0, width: 4, radius: 6, corners: 1, rotate: 0, color: 'rgba(0,0,0,0.5)',
|
||||
speed: 1, trail: 60, shadow: false, hwaccel: true, className: 'spinner', zIndex: 2e9,
|
||||
top: 'auto', left: 'auto', position: 'absolute'
|
||||
},
|
||||
|
||||
events: {
|
||||
// Close bindings
|
||||
"click .close": "_closeInfowindow",
|
||||
"touchstart .close": "_closeInfowindow",
|
||||
"MSPointerDown .close": "_closeInfowindow",
|
||||
// Rest infowindow bindings
|
||||
"dragstart": "_checkOrigin",
|
||||
"mousedown": "_checkOrigin",
|
||||
"touchstart": "_checkOrigin",
|
||||
"MSPointerDown": "_checkOrigin",
|
||||
"dblclick": "_stopPropagation",
|
||||
"DOMMouseScroll": "_stopBubbling",
|
||||
'MozMousePixelScroll': "_stopBubbling",
|
||||
"mousewheel": "_stopBubbling",
|
||||
"dbclick": "_stopPropagation",
|
||||
"click": "_stopPropagation"
|
||||
},
|
||||
|
||||
initialize: function(){
|
||||
var self = this;
|
||||
|
||||
_.bindAll(this, "render", "setLatLng", "_setTemplate", "_updatePosition",
|
||||
"_update", "toggle", "show", "hide");
|
||||
|
||||
this.mapView = this.options.mapView;
|
||||
|
||||
// Set template if it is defined in options
|
||||
if (this.options.template) this.model.set('template', this.options.template);
|
||||
|
||||
// Set template view variable and
|
||||
// compile it if it is necessary
|
||||
if (this.model.get('template')) {
|
||||
this._compileTemplate();
|
||||
} else {
|
||||
this._setTemplate();
|
||||
}
|
||||
|
||||
this.model.bind('change:content', this.render, this);
|
||||
this.model.bind('change:template_name', this._setTemplate, this);
|
||||
this.model.bind('change:latlng', this._update, this);
|
||||
this.model.bind('change:visibility', this.toggle, this);
|
||||
this.model.bind('change:template', this._compileTemplate, this);
|
||||
this.model.bind('change:sanitizeTemplate', this._compileTemplate, this);
|
||||
this.model.bind('change:alternative_names', this.render, this);
|
||||
this.model.bind('change:width', this.render, this);
|
||||
this.model.bind('change:maxHeight', this.render, this);
|
||||
|
||||
this.mapView.map.bind('change', this._updatePosition, this);
|
||||
|
||||
this.mapView.bind('zoomstart', function(){
|
||||
self.hide(true);
|
||||
});
|
||||
|
||||
this.mapView.bind('zoomend', function() {
|
||||
self.show(true);
|
||||
});
|
||||
|
||||
this.add_related_model(this.mapView.map);
|
||||
|
||||
// Hide the element
|
||||
this.$el.hide();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Render infowindow content
|
||||
*/
|
||||
render: function() {
|
||||
|
||||
if(this.template) {
|
||||
|
||||
// If there is content, destroy the jscrollpane first, then remove the content.
|
||||
var $jscrollpane = this.$(".cartodb-popup-content");
|
||||
if ($jscrollpane.length > 0 && $jscrollpane.data() != null) {
|
||||
$jscrollpane.data().jsp && $jscrollpane.data().jsp.destroy();
|
||||
}
|
||||
|
||||
// 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 : {};
|
||||
|
||||
// If a custom template is not applied, let's sanitized
|
||||
// fields for the template rendering
|
||||
if (this.model.get('template_name')) {
|
||||
var template_name = _.clone(this.model.attributes.template_name);
|
||||
|
||||
// Sanitized them
|
||||
fields = this._fieldsToString(fields, template_name);
|
||||
}
|
||||
|
||||
// Join plan fields values with content to work with
|
||||
// custom infowindows and CartoDB infowindows.
|
||||
var values = {};
|
||||
_.each(this.model.get('content').fields, function(pair) {
|
||||
values[pair.title] = pair.value;
|
||||
})
|
||||
|
||||
var obj = _.extend({
|
||||
content: {
|
||||
fields: fields,
|
||||
data: data
|
||||
}
|
||||
},values);
|
||||
|
||||
this.$el.html(
|
||||
cdb.core.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.$('.cartodb-popup').css('width', this.model.get('width') + 'px');
|
||||
}
|
||||
this.$('.cartodb-popup .cartodb-popup-content').css('max-height', this.model.get('maxHeight') + 'px');
|
||||
|
||||
// Hello jscrollpane hacks!
|
||||
// It needs some time to initialize, if not it doesn't render properly the fields
|
||||
// Check the height of the content + the header if exists
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
var actual_height = self.$(".cartodb-popup-content").outerHeight();
|
||||
if (self.model.get('maxHeight') <= actual_height)
|
||||
self.$(".cartodb-popup-content").jScrollPane({
|
||||
verticalDragMinHeight: 20,
|
||||
autoReinitialise: true
|
||||
});
|
||||
}, 1);
|
||||
|
||||
// If the infowindow is loading, show spin
|
||||
this._checkLoading();
|
||||
|
||||
// If the template is 'cover-enabled', load the cover
|
||||
this._loadCover();
|
||||
|
||||
if(!this.isLoadingData()) {
|
||||
this.model.trigger('domready', this, this.$el);
|
||||
this.trigger('domready', this, this.$el);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_getModelTemplate: function() {
|
||||
return this.model.get("template_name")
|
||||
},
|
||||
|
||||
/**
|
||||
* Change template of the infowindow
|
||||
*/
|
||||
_setTemplate: function() {
|
||||
if (this.model.get('template_name')) {
|
||||
this.template = cdb.templates.getTemplate(this._getModelTemplate());
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Compile template of the infowindow
|
||||
*/
|
||||
_compileTemplate: function() {
|
||||
var template = this.model.get('template') ?
|
||||
this.model.get('template') :
|
||||
cdb.templates.getTemplate(this._getModelTemplate());
|
||||
|
||||
if(typeof(template) !== 'function') {
|
||||
this.template = new cdb.core.Template({
|
||||
template: template,
|
||||
type: this.model.get('template_type') || 'mustache'
|
||||
}).asFunction()
|
||||
} else {
|
||||
this.template = template
|
||||
}
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check event origin
|
||||
*/
|
||||
_checkOrigin: function(ev) {
|
||||
// If the mouse down come from jspVerticalBar
|
||||
// dont stop the propagation, but if the event
|
||||
// is a touchstart, stop the propagation
|
||||
var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));
|
||||
|
||||
if (!come_from_scroll) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert values to string unless value is NULL
|
||||
*/
|
||||
_fieldsToString: function(fields, template_name) {
|
||||
var fields_sanitized = [];
|
||||
if (fields && fields.length > 0) {
|
||||
var self = this;
|
||||
fields_sanitized = _.map(fields, function(field,i) {
|
||||
// Return whole attribute sanitized
|
||||
return self._sanitizeField(field, template_name, field.index || i);
|
||||
});
|
||||
}
|
||||
return fields_sanitized;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitize fields, what does it mean?
|
||||
* - If value is null, transform to string
|
||||
* - If value is an url, add it as an attribute
|
||||
* - Cut off title if it is very long (in header or image templates).
|
||||
* - If the value is a valid url, let's make it a link.
|
||||
* - More to come...
|
||||
*/
|
||||
_sanitizeField: function(attr, template_name, pos) {
|
||||
// Check null or undefined :| and set both to empty == ''
|
||||
if (attr.value == null || attr.value == undefined) {
|
||||
attr.value = '';
|
||||
}
|
||||
|
||||
//Get the alternative title
|
||||
var alternative_name = this.model.getAlternativeName(attr.title);
|
||||
|
||||
if (attr.title && alternative_name) {
|
||||
// Alternative title
|
||||
attr.title = alternative_name;
|
||||
} else if (attr.title) {
|
||||
// Remove '_' character from titles
|
||||
attr.title = attr.title.replace(/_/g,' ');
|
||||
}
|
||||
|
||||
// Cast all values to string due to problems with Mustache 0 number rendering
|
||||
var new_value = attr.value.toString();
|
||||
|
||||
// If it is index 0, not any field type, header template type and length bigger than 30... cut off the text!
|
||||
if (!attr.type && pos==0 && attr.value.length > 35 && template_name && template_name.search('_header_') != -1) {
|
||||
new_value = attr.value.substr(0,32) + "...";
|
||||
}
|
||||
|
||||
// If it is index 1, not any field type, header image template type and length bigger than 30... cut off the text!
|
||||
if (!attr.type && pos==1 && attr.value.length > 35 && template_name && template_name.search('_header_with_image') != -1) {
|
||||
new_value = attr.value.substr(0,32) + "...";
|
||||
}
|
||||
|
||||
// Is it the value a link?
|
||||
if (this._isValidURL(attr.value)) {
|
||||
new_value = "<a href='" + attr.value + "' target='_blank'>" + new_value + "</a>"
|
||||
}
|
||||
|
||||
// If it is index 0, not any field type, header image template type... don't cut off the text or add any link!!
|
||||
if (pos==0 && template_name.search('_header_with_image') != -1) {
|
||||
new_value = attr.value;
|
||||
}
|
||||
|
||||
// Save new sanitized value
|
||||
attr.value = new_value;
|
||||
|
||||
return attr;
|
||||
},
|
||||
|
||||
isLoadingData: function() {
|
||||
var content = this.model.get("content");
|
||||
return content.fields && content.fields.length == 1 && content.fields[0].type === "loading";
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if infowindow is loading the row content
|
||||
*/
|
||||
_checkLoading: function() {
|
||||
if (this.isLoadingData()) {
|
||||
this._startSpinner();
|
||||
} else {
|
||||
this._stopSpinner();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop loading spinner
|
||||
*/
|
||||
_stopSpinner: function() {
|
||||
if (this.spinner)
|
||||
this.spinner.stop()
|
||||
},
|
||||
|
||||
/**
|
||||
* Start loading spinner
|
||||
*/
|
||||
_startSpinner: function($el) {
|
||||
this._stopSpinner();
|
||||
|
||||
var $el = this.$el.find('.loading');
|
||||
|
||||
if ($el) {
|
||||
// Check if it is dark or other to change color
|
||||
var template_dark = this.model.get('template_name').search('dark') != -1;
|
||||
|
||||
if (template_dark) {
|
||||
this.spin_options.color = '#FFF';
|
||||
} else {
|
||||
this.spin_options.color = 'rgba(0,0,0,0.5)';
|
||||
}
|
||||
|
||||
this.spinner = new Spinner(this.spin_options).spin();
|
||||
$el.append(this.spinner.el);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop loading spinner
|
||||
*/
|
||||
_containsCover: function() {
|
||||
return this.$el.find(".cartodb-popup.header").attr("data-cover") ? true : false;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get cover URL
|
||||
*/
|
||||
_getCoverURL: function() {
|
||||
var content = this.model.get("content");
|
||||
|
||||
if (content && content.fields && content.fields.length > 0) {
|
||||
return (content.fields[0].value || '').toString();
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attempts to load the cover URL and show it
|
||||
*/
|
||||
_loadCover: function() {
|
||||
|
||||
if (!this._containsCover()) return;
|
||||
|
||||
var self = this;
|
||||
var $cover = this.$(".cover");
|
||||
var $img = $cover.find("img");
|
||||
var $shadow = this.$(".shadow");
|
||||
var url = this._getCoverURL();
|
||||
|
||||
if (!this._isValidURL(url)) {
|
||||
$img.hide();
|
||||
$shadow.hide();
|
||||
cdb.log.info("Header image url not valid");
|
||||
return;
|
||||
}
|
||||
|
||||
// configure spinner
|
||||
var target = document.getElementById('spinner');
|
||||
var opts = { lines: 9, length: 4, width: 2, radius: 4, corners: 1, rotate: 0, color: '#ccc', speed: 1, trail: 60, shadow: true, hwaccel: false, zIndex: 2e9 };
|
||||
var spinner = new Spinner(opts).spin(target);
|
||||
|
||||
// create the image
|
||||
|
||||
$img.hide(function() {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
$img = $("<img />").attr("src", url);
|
||||
$cover.append($img);
|
||||
|
||||
$img.load(function(){
|
||||
spinner.stop();
|
||||
|
||||
var w = $img.width();
|
||||
var h = $img.height();
|
||||
var coverWidth = $cover.width();
|
||||
var coverHeight = $cover.height();
|
||||
|
||||
var ratio = h / w;
|
||||
var coverRatio = coverHeight / coverWidth;
|
||||
|
||||
// Resize rules
|
||||
if ( w > coverWidth && h > coverHeight) { // bigger image
|
||||
if ( ratio < coverRatio ) $img.css({ height: coverHeight });
|
||||
else {
|
||||
var calculatedHeight = h / (w / coverWidth);
|
||||
$img.css({ width: coverWidth, top: "50%", position: "absolute", "margin-top": -1*parseInt(calculatedHeight, 10)/2 });
|
||||
}
|
||||
} else {
|
||||
var calculatedHeight = h / (w / coverWidth);
|
||||
$img.css({ width: coverWidth, top: "50%", position: "absolute", "margin-top": -1*parseInt(calculatedHeight, 10)/2 });
|
||||
}
|
||||
|
||||
$img.fadeIn(300);
|
||||
})
|
||||
.error(function(){
|
||||
spinner.stop();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the provided URL is valid
|
||||
*/
|
||||
_isValidURL: function(url) {
|
||||
if (url) {
|
||||
var urlPattern = /^(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-|]*[\w@?^=%&\/~+#-])?$/
|
||||
return String(url).match(urlPattern) != null ? true : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle infowindow visibility
|
||||
*/
|
||||
toggle: function() {
|
||||
this.model.get("visibility") ? this.show() : this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop event bubbling
|
||||
*/
|
||||
_stopBubbling: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop event propagation
|
||||
*/
|
||||
_stopPropagation: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set loading state adding its content
|
||||
*/
|
||||
setLoading: function() {
|
||||
this.model.set({
|
||||
content: {
|
||||
fields: [{
|
||||
title: null,
|
||||
alternative_name: null,
|
||||
value: 'Loading content...',
|
||||
index: null,
|
||||
type: "loading"
|
||||
}],
|
||||
data: {}
|
||||
}
|
||||
})
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set loading state adding its content
|
||||
*/
|
||||
setError: function() {
|
||||
this.model.set({
|
||||
content: {
|
||||
fields: [{
|
||||
title: null,
|
||||
alternative_name: null,
|
||||
value: 'There has been an error...',
|
||||
index: null,
|
||||
type: 'error'
|
||||
}],
|
||||
data: {}
|
||||
}
|
||||
})
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the correct position for the popup
|
||||
*/
|
||||
setLatLng: function (latlng) {
|
||||
this.model.set("latlng", latlng);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close infowindow
|
||||
*/
|
||||
_closeInfowindow: function(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
if (this.model.get("visibility")) {
|
||||
this.model.set("visibility", false);
|
||||
this.trigger('close');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set visibility infowindow
|
||||
*/
|
||||
showInfowindow: function() {
|
||||
this.model.set("visibility", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show infowindow (update, pan, etc)
|
||||
*/
|
||||
show: function (no_pan) {
|
||||
var self = this;
|
||||
|
||||
if (this.model.get("visibility")) {
|
||||
self.$el.css({ left: -5000 });
|
||||
self._update(no_pan);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get infowindow visibility
|
||||
*/
|
||||
isHidden: function () {
|
||||
return !this.model.get("visibility");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set infowindow to hidden
|
||||
*/
|
||||
hide: function (force) {
|
||||
if (force || !this.model.get("visibility")) this._animateOut();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update infowindow
|
||||
*/
|
||||
_update: function (no_pan) {
|
||||
|
||||
if(!this.isHidden()) {
|
||||
var delay = 0;
|
||||
|
||||
if (!no_pan) {
|
||||
var delay = this.adjustPan();
|
||||
}
|
||||
|
||||
this._updatePosition();
|
||||
this._animateIn(delay);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Animate infowindow to show up
|
||||
*/
|
||||
_animateIn: function(delay) {
|
||||
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.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 (!$.browser.msie || ($.browser.msie && parseInt($.browser.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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the position (private)
|
||||
*/
|
||||
_updatePosition: function () {
|
||||
if(this.isHidden()) return;
|
||||
|
||||
var
|
||||
offset = this.model.get("offset"),
|
||||
pos = this.mapView.latLonToPixel(this.model.get("latlng")),
|
||||
x = this.$el.position().left,
|
||||
y = this.$el.position().top,
|
||||
containerHeight = this.$el.outerHeight(true),
|
||||
containerWidth = this.$el.width(),
|
||||
left = pos.x - offset[0],
|
||||
size = this.mapView.getSize(),
|
||||
bottom = -1*(pos.y - offset[1] - size.y);
|
||||
|
||||
this.$el.css({ bottom: bottom, left: left });
|
||||
},
|
||||
|
||||
/**
|
||||
* Adjust pan to show correctly the infowindow
|
||||
*/
|
||||
adjustPan: function () {
|
||||
var offset = this.model.get("offset");
|
||||
|
||||
if (!this.model.get("autoPan") || this.isHidden()) { return; }
|
||||
|
||||
var
|
||||
x = this.$el.position().left,
|
||||
y = this.$el.position().top,
|
||||
containerHeight = this.$el.outerHeight(true) + 15, // Adding some more space
|
||||
containerWidth = this.$el.width(),
|
||||
pos = this.mapView.latLonToPixel(this.model.get("latlng")),
|
||||
adjustOffset = {x: 0, y: 0};
|
||||
size = this.mapView.getSize()
|
||||
wait_callback = 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);
|
||||
wait_callback = 300;
|
||||
}
|
||||
|
||||
return wait_callback;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,238 @@
|
||||
|
||||
/**
|
||||
* Layer selector: it allows to select the layers that will be shown in the map
|
||||
* - It needs the mapview, the element template and the dropdown template
|
||||
*
|
||||
* var layer_selector = new cdb.geo.ui.LayerSelector({
|
||||
* mapView: mapView,
|
||||
* template: element_template,
|
||||
* dropdown_template: dropdown_template
|
||||
* });
|
||||
*/
|
||||
|
||||
cdb.geo.ui.LayerSelector = cdb.core.View.extend({
|
||||
|
||||
className: 'cartodb-layer-selector-box',
|
||||
|
||||
events: {
|
||||
"click": '_openDropdown',
|
||||
"dblclick": 'killEvent',
|
||||
"mousedown": 'killEvent'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.map = this.options.mapView.map;
|
||||
|
||||
this.mapView = this.options.mapView;
|
||||
this.mapView.bind('click zoomstart drag', function() {
|
||||
this.dropdown && this.dropdown.hide()
|
||||
}, this);
|
||||
this.add_related_model(this.mapView);
|
||||
|
||||
this.layers = [];
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
this.$el.html(this.options.template(this.options));
|
||||
|
||||
this.dropdown = new cdb.ui.common.Dropdown({
|
||||
className:"cartodb-dropdown border",
|
||||
template: this.options.dropdown_template,
|
||||
target: this.$el.find("a"),
|
||||
speedIn: 300,
|
||||
speedOut: 200,
|
||||
position: "position",
|
||||
tick: "right",
|
||||
vertical_position: "down",
|
||||
horizontal_position: "right",
|
||||
vertical_offset: 7,
|
||||
horizontal_offset: 13
|
||||
});
|
||||
|
||||
if (cdb.god) cdb.god.bind("closeDialogs", this.dropdown.hide, this.dropdown);
|
||||
|
||||
this.$el.append(this.dropdown.render().el);
|
||||
|
||||
this._getLayers();
|
||||
this._setCount();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_getLayers: function() {
|
||||
var self = this;
|
||||
this.layers = [];
|
||||
|
||||
_.each(this.map.layers.models, function(layer) {
|
||||
|
||||
if (layer.get("type") == 'layergroup' || layer.get('type') === 'namedmap') {
|
||||
var layerGroupView = self.mapView.getLayerByCid(layer.cid);
|
||||
for (var i = 0 ; i < layerGroupView.getLayerCount(); ++i) {
|
||||
var l = layerGroupView.getLayer(i);
|
||||
var m = new cdb.core.Model(l);
|
||||
m.set('order', i);
|
||||
m.set('type', 'layergroup');
|
||||
|
||||
if (m.get("visible") === undefined) m.set('visible', true);
|
||||
|
||||
m.bind('change:visible', function(model) {
|
||||
this.trigger("change:visible", model.get('visible'), model.get('order'), model);
|
||||
}, self);
|
||||
|
||||
if(self.options.layer_names) {
|
||||
m.set('layer_name', self.options.layer_names[i]);
|
||||
} else {
|
||||
m.set('layer_name', l.options.layer_name);
|
||||
}
|
||||
|
||||
var layerView = self._createLayer('LayerViewFromLayerGroup', {
|
||||
model: m,
|
||||
layerView: layerGroupView,
|
||||
layerIndex: i
|
||||
});
|
||||
layerView.bind('switchChanged', self._setCount, self);
|
||||
self.layers.push(layerView);
|
||||
}
|
||||
} else if (layer.get("type") === "CartoDB" || layer.get('type') === 'torque') {
|
||||
var layerView = self._createLayer('LayerView', { model: layer });
|
||||
layerView.bind('switchChanged', self._setCount, self);
|
||||
self.layers.push(layerView);
|
||||
layerView.model.bind('change:visible', function(model) {
|
||||
this.trigger("change:visible", model.get('visible'), model.get('order'), model);
|
||||
}, self);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
_createLayer: function(_class, opts) {
|
||||
var layerView = new cdb.geo.ui[_class](opts);
|
||||
this.$("ul").append(layerView.render().el);
|
||||
this.addView(layerView);
|
||||
return layerView;
|
||||
},
|
||||
|
||||
_setCount: function() {
|
||||
var count = 0;
|
||||
for (var i = 0, l = this.layers.length; i < l; ++i) {
|
||||
var lyr = this.layers[i];
|
||||
|
||||
if (lyr.model.get('visible')) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
this.$('.count').text(count);
|
||||
this.trigger("switchChanged", this);
|
||||
},
|
||||
|
||||
_openDropdown: function() {
|
||||
this.dropdown.open();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* View for each CartoDB layer
|
||||
* - It needs a model to make it work.
|
||||
*
|
||||
* var layerView = new cdb.geo.ui.LayerView({
|
||||
* model: layer_model,
|
||||
* layer_definition: layer_definition
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
cdb.geo.ui.LayerView = cdb.core.View.extend({
|
||||
|
||||
tagName: "li",
|
||||
|
||||
defaults: {
|
||||
template: '\
|
||||
<a class="layer" href="#/change-layer"><%- layer_name %></a>\
|
||||
<a href="#switch" class="right <%- visible ? "enabled" : "disabled" %> switch"><span class="handle"></span></a>\
|
||||
'
|
||||
},
|
||||
|
||||
events: {
|
||||
"click": '_onSwitchClick'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
if (!this.model.has('visible')) this.model.set('visible', false);
|
||||
|
||||
this.model.bind("change:visible", this._onSwitchSelected, this);
|
||||
|
||||
this.add_related_model(this.model);
|
||||
|
||||
this._onSwitchSelected();
|
||||
|
||||
// Template
|
||||
this.template = this.options.template ? cdb.templates.getTemplate(this.options.template) : _.template(this.defaults.template);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var attrs = _.clone(this.model.attributes);
|
||||
attrs.layer_name = attrs.layer_name || attrs.table_name;
|
||||
this.$el.append(this.template(attrs));
|
||||
return this;
|
||||
},
|
||||
|
||||
/*
|
||||
* Throw an event when the user clicks in the switch button
|
||||
*/
|
||||
_onSwitchSelected: function() {
|
||||
var enabled = this.model.get('visible');
|
||||
|
||||
// Change switch
|
||||
this.$el.find(".switch")
|
||||
.removeClass(enabled ? 'disabled' : 'enabled')
|
||||
.addClass(enabled ? 'enabled' : 'disabled');
|
||||
|
||||
// Send trigger
|
||||
this.trigger('switchChanged');
|
||||
|
||||
},
|
||||
|
||||
_onSwitchClick: function(e){
|
||||
this.killEvent(e);
|
||||
|
||||
// Set model
|
||||
this.model.set("visible", !this.model.get("visible"));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* View for each layer from a layer group
|
||||
* - It needs a model and the layer_definition to make it work.
|
||||
*
|
||||
* var layerView = new cdb.geo.ui.LayerViewFromLayerGroup({
|
||||
* model: layer_model,
|
||||
* layerView: layweView
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
cdb.geo.ui.LayerViewFromLayerGroup = cdb.geo.ui.LayerView.extend({
|
||||
|
||||
_onSwitchSelected: function() {
|
||||
|
||||
cdb.geo.ui.LayerView.prototype._onSwitchSelected.call(this);
|
||||
var sublayer = this.options.layerView.getSubLayer(this.options.layerIndex)
|
||||
var visible = this.model.get('visible');
|
||||
|
||||
if (visible) {
|
||||
sublayer.show();
|
||||
} else {
|
||||
sublayer.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,818 @@
|
||||
cdb.geo.ui.MobileLayer = cdb.core.View.extend({
|
||||
|
||||
events: {
|
||||
'click h3': "_toggle",
|
||||
"dblclick": "_stopPropagation"
|
||||
},
|
||||
|
||||
tagName: "li",
|
||||
|
||||
className: "cartodb-mobile-layer has-toggle",
|
||||
|
||||
template: cdb.core.Template.compile("<% if (show_title) { %><h3><%- layer_name %><% } %><a href='#' class='toggle<%- toggle_class %>'></a></h3>"),
|
||||
|
||||
/**
|
||||
* Stop event propagation
|
||||
*/
|
||||
_stopPropagation: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.model.bind("change:visible", this._onChangeVisible, this);
|
||||
|
||||
},
|
||||
|
||||
_onChangeVisible: function() {
|
||||
|
||||
this.$el.find(".legend")[ this.model.get("visible") ? "fadeIn":"fadeOut"](150);
|
||||
this.$el[ this.model.get("visible") ? "removeClass":"addClass"]("hidden");
|
||||
|
||||
this.trigger("change_visibility", this);
|
||||
|
||||
},
|
||||
|
||||
_toggle: function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.options.hide_toggle) return;
|
||||
|
||||
this.model.set("visible", !this.model.get("visible"))
|
||||
|
||||
},
|
||||
|
||||
_renderLegend: function() {
|
||||
|
||||
if (!this.options.show_legends) return;
|
||||
|
||||
if (this.model.get("legend") && (this.model.get("legend").type == "none" || !this.model.get("legend").type)) return;
|
||||
if (this.model.get("legend") && this.model.get("legend").items && this.model.get("legend").items.length == 0) return;
|
||||
|
||||
this.$el.addClass("has-legend");
|
||||
|
||||
var legend = new cdb.geo.ui.Legend(this.model.get("legend"));
|
||||
|
||||
legend.undelegateEvents();
|
||||
|
||||
this.$el.append(legend.render().$el);
|
||||
|
||||
},
|
||||
|
||||
_truncate: function(input, length) {
|
||||
return input.substr(0, length-1) + (input.length > length ? '…' : '');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
var layer_name = this.model.get("layer_name");
|
||||
|
||||
layer_name = layer_name ? this._truncate(layer_name, 23) : "untitled";
|
||||
|
||||
var attributes = _.extend(
|
||||
this.model.attributes,
|
||||
{
|
||||
layer_name: this.options.show_title ? layer_name : "",
|
||||
toggle_class: this.options.hide_toggle ? " hide" : ""
|
||||
}
|
||||
);
|
||||
|
||||
this.$el.html(this.template(_.extend(attributes, { show_title: this.options.show_title } )));
|
||||
|
||||
|
||||
if (this.options.hide_toggle) this.$el.removeClass("has-toggle");
|
||||
if (!this.model.get("visible")) this.$el.addClass("hidden");
|
||||
if (this.model.get("legend")) this._renderLegend();
|
||||
|
||||
this._onChangeVisible();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
cdb.geo.ui.Mobile = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-mobile",
|
||||
|
||||
events: {
|
||||
"click .cartodb-attribution-button": "_onAttributionClick",
|
||||
"click .toggle": "_toggle",
|
||||
"click .fullscreen": "_toggleFullScreen",
|
||||
"click .backdrop": "_onBackdropClick",
|
||||
"dblclick .aside": "_stopPropagation",
|
||||
"dragstart .aside": "_checkOrigin",
|
||||
"mousedown .aside": "_checkOrigin",
|
||||
"touchstart .aside": "_checkOrigin",
|
||||
"MSPointerDown .aside": "_checkOrigin",
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.bindAll(this, "_toggle", "_reInitScrollpane");
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.hasLayerSelector = false;
|
||||
this.layersLoading = 0;
|
||||
|
||||
this.slides_data = this.options.slides_data;
|
||||
this.visualization = this.options.visualization;
|
||||
|
||||
if (this.visualization) {
|
||||
this.slides = this.visualization.slides;
|
||||
}
|
||||
|
||||
this.mobileEnabled = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
this.visibility_options = this.options.visibility_options || {};
|
||||
|
||||
this.mapView = this.options.mapView;
|
||||
this.map = this.mapView.map;
|
||||
|
||||
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/zoom');
|
||||
|
||||
this._selectOverlays();
|
||||
|
||||
this._setupModel();
|
||||
|
||||
window.addEventListener('orientationchange', _.bind(this.doOnOrientationChange, this));
|
||||
|
||||
this._addWheelEvent();
|
||||
|
||||
},
|
||||
|
||||
loadingTiles: function() {
|
||||
if (this.loader) {
|
||||
this.loader.show()
|
||||
}
|
||||
|
||||
if (this.layersLoading === 0) {
|
||||
this.trigger('loading');
|
||||
}
|
||||
this.layersLoading++;
|
||||
},
|
||||
|
||||
loadTiles: function() {
|
||||
if (this.loader) {
|
||||
this.loader.hide();
|
||||
}
|
||||
this.layersLoading--;
|
||||
// check less than 0 because loading event sometimes is
|
||||
// thrown before visualization creation
|
||||
if(this.layersLoading <= 0) {
|
||||
this.layersLoading = 0;
|
||||
this.trigger('load');
|
||||
}
|
||||
},
|
||||
|
||||
_selectOverlays: function() {
|
||||
|
||||
if (this.slides && this.slides_data) { // if there are slides…
|
||||
|
||||
var state = this.slides.state();
|
||||
|
||||
if (state == 0) this.overlays = this.options.overlays; // first slide == master vis
|
||||
else {
|
||||
this.overlays = this.slides_data[state - 1].overlays;
|
||||
}
|
||||
} else { // otherwise we load the regular overlays
|
||||
this.overlays = this.options.overlays;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_addWheelEvent: function() {
|
||||
|
||||
var self = this;
|
||||
var mapView = this.options.mapView;
|
||||
|
||||
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {
|
||||
|
||||
if ( !document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
|
||||
mapView.options.map.set("scrollwheel", false);
|
||||
}
|
||||
|
||||
mapView.invalidateSize();
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
_setupModel: function() {
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
open: false,
|
||||
layer_count: 0
|
||||
});
|
||||
|
||||
this.model.on("change:open", this._onChangeOpen, this);
|
||||
this.model.on("change:layer_count", this._onChangeLayerCount, this);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Check event origin
|
||||
*/
|
||||
_checkOrigin: function(ev) {
|
||||
// If the mouse down come from jspVerticalBar
|
||||
// dont stop the propagation, but if the event
|
||||
// is a touchstart, stop the propagation
|
||||
var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));
|
||||
|
||||
if (!come_from_scroll) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
_stopPropagation: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
_onBackdropClick: function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.$el.find(".backdrop").fadeOut(250);
|
||||
this.$el.find(".cartodb-attribution").fadeOut(250);
|
||||
|
||||
},
|
||||
|
||||
_onAttributionClick: function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.$el.find(".backdrop").fadeIn(250);
|
||||
this.$el.find(".cartodb-attribution").fadeIn(250);
|
||||
|
||||
},
|
||||
|
||||
_toggle: function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.model.set("open", !this.model.get("open"));
|
||||
|
||||
},
|
||||
|
||||
_toggleFullScreen: function(ev) {
|
||||
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
var doc = window.document;
|
||||
var docEl = $("#map > div")[0];
|
||||
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen;
|
||||
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen;
|
||||
|
||||
var mapView = this.options.mapView;
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement) {
|
||||
|
||||
requestFullScreen.call(docEl);
|
||||
|
||||
if (mapView) {
|
||||
|
||||
mapView.options.map.set("scrollwheel", true);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
cancelFullScreen.call(doc);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
_open: function() {
|
||||
|
||||
var right = this.$el.find(".aside").width();
|
||||
|
||||
this.$el.find(".cartodb-header").animate({ right: right }, 200)
|
||||
this.$el.find(".aside").animate({ right: 0 }, 200)
|
||||
this.$el.find(".cartodb-attribution-button").animate({ right: right + parseInt(this.$el.find(".cartodb-attribution-button").css("right")) }, 200)
|
||||
this.$el.find(".cartodb-attribution").animate({ right: right + parseInt(this.$el.find(".cartodb-attribution-button").css("right")) }, 200)
|
||||
this._initScrollPane();
|
||||
|
||||
},
|
||||
|
||||
_close: function() {
|
||||
|
||||
this.$el.find(".cartodb-header").animate({ right: 0 }, 200)
|
||||
this.$el.find(".aside").animate({ right: - this.$el.find(".aside").width() }, 200)
|
||||
this.$el.find(".cartodb-attribution-button").animate({ right: 20 }, 200)
|
||||
this.$el.find(".cartodb-attribution").animate({ right: 20 }, 200)
|
||||
|
||||
},
|
||||
|
||||
default_options: {
|
||||
timeout: 0,
|
||||
msg: ''
|
||||
},
|
||||
|
||||
_stopPropagation: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
doOnOrientationChange: function() {
|
||||
|
||||
switch(window.orientation)
|
||||
{
|
||||
case -90:
|
||||
case 90: this.recalc("landscape");
|
||||
break;
|
||||
default: this.recalc("portrait");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
recalc: function(orientation) {
|
||||
|
||||
var height = $(".legends > div.cartodb-legend-stack").height();
|
||||
|
||||
if (this.$el.hasClass("open") && height < 100 && !this.$el.hasClass("torque")) {
|
||||
|
||||
this.$el.css("height", height);
|
||||
this.$el.find(".top-shadow").hide();
|
||||
this.$el.find(".bottom-shadow").hide();
|
||||
|
||||
} else if (this.$el.hasClass("open") && height < 100 && this.$el.hasClass("legends") && this.$el.hasClass("torque")) {
|
||||
|
||||
this.$el.css("height", height + $(".legends > div.torque").height() );
|
||||
this.$el.find(".top-shadow").hide();
|
||||
this.$el.find(".bottom-shadow").hide();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_onChangeLayerCount: function() {
|
||||
|
||||
var layer_count = this.model.get("layer_count");
|
||||
var msg = layer_count + " layer" + (layer_count != 1 ? "s" : "");
|
||||
this.$el.find(".aside .layer-container > h3").html(msg);
|
||||
|
||||
},
|
||||
|
||||
_onChangeOpen: function() {
|
||||
this.model.get("open") ? this._open() : this._close();
|
||||
},
|
||||
|
||||
_createLayer: function(_class, opts) {
|
||||
return new cdb.geo.ui[_class](opts);
|
||||
},
|
||||
|
||||
_getLayers: function() {
|
||||
|
||||
this.layers = [];
|
||||
|
||||
// we add the layers to the array depending on the method used
|
||||
// to sent us the layers
|
||||
if (this.options.layerView) {
|
||||
this._getLayersFromLayerView();
|
||||
} else {
|
||||
_.each(this.map.layers.models, this._getLayer, this);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_getLayersFromLayerView: function() {
|
||||
|
||||
if (this.options.layerView && this.options.layerView.model.get("type") == "layergroup") {
|
||||
|
||||
this.layers = _.map(this.options.layerView.layers, function(l, i) {
|
||||
|
||||
var m = new cdb.core.Model(l);
|
||||
|
||||
m.set('order', i);
|
||||
m.set('type', 'layergroup');
|
||||
m.set('visible', l.visible);
|
||||
m.set('layer_name', l.options.layer_name);
|
||||
|
||||
layerView = this._createLayer('LayerViewFromLayerGroup', {
|
||||
model: m,
|
||||
layerView: this.options.layerView,
|
||||
layerIndex: i
|
||||
});
|
||||
|
||||
return layerView.model;
|
||||
|
||||
}, this);
|
||||
|
||||
} else if (this.options.layerView && (this.options.layerView.model.get("type") == "torque")) {
|
||||
|
||||
var layerView = this._createLayer('LayerView', { model: this.options.layerView.model });
|
||||
|
||||
this.layers.push(layerView.model);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
_getLayer: function(layer) {
|
||||
|
||||
if (layer.get("type") == 'layergroup' || layer.get('type') === 'namedmap') {
|
||||
|
||||
var layerGroupView = this.mapView.getLayerByCid(layer.cid);
|
||||
|
||||
for (var i = 0 ; i < layerGroupView.getLayerCount(); ++i) {
|
||||
|
||||
var l = layerGroupView.getLayer(i);
|
||||
var m = new cdb.core.Model(l);
|
||||
|
||||
m.set('order', i);
|
||||
m.set('type', 'layergroup');
|
||||
m.set('visible', l.visible);
|
||||
m.set('layer_name', l.options.layer_name);
|
||||
|
||||
layerView = this._createLayer('LayerViewFromLayerGroup', {
|
||||
model: m,
|
||||
layerView: layerGroupView,
|
||||
layerIndex: i
|
||||
});
|
||||
|
||||
this.layers.push(layerView.model);
|
||||
|
||||
}
|
||||
|
||||
} else if (layer.get("type") === "CartoDB" || layer.get('type') === 'torque') {
|
||||
|
||||
if (layer.get('type') === 'torque') {
|
||||
layer.on("change:visible", this._toggleSlider, this);
|
||||
}
|
||||
|
||||
this.layers.push(layer);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_toggleSlider: function(m) {
|
||||
|
||||
if (m.get("visible")) {
|
||||
this.$el.addClass("with-torque");
|
||||
this.slider.show();
|
||||
} else {
|
||||
this.$el.removeClass("with-torque");
|
||||
this.slider.hide();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_reInitScrollpane: function() {
|
||||
this.$('.scrollpane').data('jsp') && this.$('.scrollpane').data('jsp').reinitialise();
|
||||
},
|
||||
|
||||
_bindOrientationChange: function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
var onOrientationChange = function() {
|
||||
$(".cartodb-mobile .scrollpane").css("max-height", self.$el.height() - 30);
|
||||
$('.cartodb-mobile .scrollpane').data('jsp') && $('.cartodb-mobile .scrollpane').data('jsp').reinitialise();
|
||||
};
|
||||
|
||||
if (!window.addEventListener) {
|
||||
window.attachEvent('orientationchange', onOrientationChange, this);
|
||||
} else {
|
||||
window.addEventListener('orientationchange', _.bind(onOrientationChange));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_renderOverlays: function() {
|
||||
|
||||
var hasSearchOverlay = false;
|
||||
var hasZoomOverlay = false;
|
||||
var hasLoaderOverlay = false;
|
||||
var hasLayerSelector = false;
|
||||
|
||||
_.each(this.overlays, function(overlay) {
|
||||
|
||||
if (!this.visibility_options.search && overlay.type == 'search') {
|
||||
if (this.visibility_options.search !== false && this.visibility_options.search !== "false") {
|
||||
this._addSearch();
|
||||
hasSearchOverlay = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.visibility_options.zoomControl && overlay.type === 'zoom') {
|
||||
if (this.visibility_options.zoomControl !== "false") {
|
||||
this._addZoom();
|
||||
hasZoomOverlay = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.visibility_options.loaderControl && overlay.type === 'loader') {
|
||||
if (this.visibility_options.loaderControl !== "false") {
|
||||
this._addLoader();
|
||||
hasLoaderOverlay = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlay.type == 'fullscreen' && !this.mobileEnabled) {
|
||||
this._addFullscreen();
|
||||
}
|
||||
|
||||
if (overlay.type == 'header') {
|
||||
this._addHeader(overlay);
|
||||
}
|
||||
|
||||
if (overlay.type == 'layer_selector') {
|
||||
hasLayerSelector = true;
|
||||
}
|
||||
|
||||
}, this);
|
||||
|
||||
var search_visibility = this.visibility_options.search === true || this.visibility_options.search === "true";
|
||||
var zoom_visibility = this.visibility_options.zoomControl === true || this.visibility_options.zoomControl === "true";
|
||||
var loader_visibility = this.visibility_options.loaderControl === true || this.visibility_options.loaderControl === "true";
|
||||
var layer_selector_visibility = this.visibility_options.layer_selector;
|
||||
|
||||
if (!hasSearchOverlay && search_visibility) this._addSearch();
|
||||
if (!hasZoomOverlay && zoom_visibility) this._addZoom();
|
||||
if (!hasLoaderOverlay && loader_visibility) this._addLoader();
|
||||
if (layer_selector_visibility || hasLayerSelector && layer_selector_visibility == undefined) this.hasLayerSelector = true;
|
||||
|
||||
},
|
||||
|
||||
_initScrollPane: function() {
|
||||
|
||||
if (this.$scrollpane) return;
|
||||
|
||||
var self = this;
|
||||
|
||||
var height = this.$el.height();
|
||||
this.$scrollpane = this.$el.find(".scrollpane");
|
||||
|
||||
setTimeout(function() {
|
||||
self.$scrollpane.css("max-height", height - 60);
|
||||
self.$scrollpane.jScrollPane({ showArrows: true });
|
||||
}, 500);
|
||||
|
||||
},
|
||||
|
||||
_addZoom: function() {
|
||||
|
||||
var template = cdb.core.Template.compile('\
|
||||
<a href="#zoom_in" class="zoom_in">+</a>\
|
||||
<a href="#zoom_out" class="zoom_out">-</a>\
|
||||
<div class="info"></div>', 'mustache'
|
||||
);
|
||||
|
||||
var zoom = new cdb.geo.ui.Zoom({
|
||||
model: this.options.map,
|
||||
template: template
|
||||
});
|
||||
|
||||
this.$el.append(zoom.render().$el);
|
||||
this.$el.addClass("with-zoom");
|
||||
|
||||
},
|
||||
|
||||
_addLoader: function() {
|
||||
|
||||
var template = cdb.core.Template.compile('<div class="loader"></div>', 'mustache');
|
||||
|
||||
this.loader = new cdb.geo.ui.TilesLoader({
|
||||
template: template
|
||||
});
|
||||
|
||||
this.$el.append(this.loader.render().$el);
|
||||
this.$el.addClass("with-loader");
|
||||
|
||||
},
|
||||
|
||||
_addFullscreen: function() {
|
||||
|
||||
if (this.visibility_options.fullscreen != false) {
|
||||
this.hasFullscreen = true;
|
||||
this.$el.addClass("with-fullscreen");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_addSearch: function() {
|
||||
|
||||
this.hasSearch = true;
|
||||
|
||||
var template = cdb.core.Template.compile('\
|
||||
<form>\
|
||||
<span class="loader"></span>\
|
||||
<input type="text" class="text" placeholder="Search for places..." value="" />\
|
||||
<input type="submit" class="submit" value="" />\
|
||||
</form>\
|
||||
', 'mustache'
|
||||
);
|
||||
|
||||
var search = new cdb.geo.ui.Search({
|
||||
template: template,
|
||||
mapView: this.mapView,
|
||||
model: this.mapView.map
|
||||
});
|
||||
|
||||
this.$el.find(".aside").prepend(search.render().$el);
|
||||
this.$el.find(".cartodb-searchbox").show();
|
||||
this.$el.addClass("with-search");
|
||||
|
||||
},
|
||||
|
||||
_addHeader: function(overlay) {
|
||||
|
||||
this.hasHeader = true;
|
||||
|
||||
this.$header = this.$el.find(".cartodb-header");
|
||||
|
||||
var title_template = _.template('<div class="hgroup"><% if (show_title) { %><div class="title"><%= title %></div><% } %><% if (show_description) { %><div class="description"><%= description %><% } %></div></div>');
|
||||
|
||||
var extra = overlay.options.extra;
|
||||
var has_header = false;
|
||||
var show_title = false, show_description = false;
|
||||
|
||||
if (extra) {
|
||||
|
||||
if (this.visibility_options.title || this.visibility_options.title != false && extra.show_title) {
|
||||
has_header = true;
|
||||
show_title = true;
|
||||
}
|
||||
|
||||
if (this.visibility_options.description || this.visibility_options.description != false && extra.show_description) {
|
||||
has_header = true;
|
||||
show_description = true;
|
||||
}
|
||||
|
||||
if (this.slides) {
|
||||
has_header = true;
|
||||
}
|
||||
|
||||
var $hgroup = title_template({
|
||||
title: cdb.core.sanitize.html(extra.title),
|
||||
show_title:show_title,
|
||||
description: cdb.core.sanitize.html(extra.description),
|
||||
show_description: show_description
|
||||
});
|
||||
|
||||
if (has_header) {
|
||||
this.$el.addClass("with-header");
|
||||
this.$header.find(".content").append($hgroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_addAttributions: function() {
|
||||
|
||||
var attributions = "";
|
||||
|
||||
this.options.mapView.$el.find(".leaflet-control-attribution").hide(); // TODO: remove this from here
|
||||
|
||||
if (this.options.layerView) {
|
||||
|
||||
attributions = this.options.layerView.model.get("attribution");
|
||||
this.$el.find(".cartodb-attribution").append(attributions);
|
||||
|
||||
} else if (this.options.map.get("attribution")) {
|
||||
|
||||
attributions = this.options.map.get("attribution");
|
||||
|
||||
_.each(attributions, function(attribution) {
|
||||
var $li = $("<li></li>");
|
||||
var $el = $li.html(attribution);
|
||||
this.$el.find(".cartodb-attribution").append($li);
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
if (attributions) {
|
||||
this.$el.find(".cartodb-attribution-button").fadeIn(250);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_renderLayers: function() {
|
||||
|
||||
var hasLegendOverlay = this.visibility_options.legends;
|
||||
|
||||
var legends = this.layers.filter(function(layer) {
|
||||
return layer.get("legend") && layer.get("legend").type !== "none"
|
||||
});
|
||||
|
||||
var hasLegends = legends.length ? true : false;
|
||||
|
||||
if (!this.hasLayerSelector && !hasLegendOverlay) return;
|
||||
if (!this.hasLayerSelector && !hasLegends) return;
|
||||
if (this.layers.length == 0) return;
|
||||
if (this.layers.length == 1 && !hasLegends) return;
|
||||
|
||||
this.$el.addClass("with-layers");
|
||||
|
||||
this.model.set("layer_count", 0);
|
||||
|
||||
if (!this.hasSearch) this.$el.find(".aside .layer-container").prepend("<h3></h3>");
|
||||
|
||||
_.each(this.layers, this._renderLayer, this);
|
||||
|
||||
},
|
||||
|
||||
_renderLayer: function(data) {
|
||||
|
||||
var hasLegend = data.get("legend") && data.get("legend").type !== "" && data.get("legend").type !== "none";
|
||||
|
||||
// When the layer selector is disabled, don't show the layer if it doesn't have legends
|
||||
if (!this.hasLayerSelector && !hasLegend) return;
|
||||
if (!this.hasLayerSelector && !data.get("visible")) return;
|
||||
|
||||
var hide_toggle = (this.layers.length == 1 || !this.hasLayerSelector);
|
||||
|
||||
var show_legends = true;
|
||||
|
||||
if (this.visibility_options && this.visibility_options.legends !== undefined) {
|
||||
show_legends = this.visibility_options.legends;
|
||||
}
|
||||
|
||||
var layer = new cdb.geo.ui.MobileLayer({
|
||||
model: data,
|
||||
show_legends: show_legends,
|
||||
show_title: !this.hasLayerSelector ? false : true,
|
||||
hide_toggle: hide_toggle
|
||||
});
|
||||
|
||||
this.$el.find(".aside .layers").append(layer.render().$el);
|
||||
|
||||
layer.bind("change_visibility", this._reInitScrollpane, this);
|
||||
|
||||
this.model.set("layer_count", this.model.get("layer_count") + 1);
|
||||
|
||||
},
|
||||
|
||||
_renderTorque: function() {
|
||||
|
||||
if (this.options.torqueLayer) {
|
||||
|
||||
this.hasTorque = true;
|
||||
|
||||
this.slider = new cdb.geo.ui.TimeSlider({type: "time_slider", layer: this.options.torqueLayer, map: this.options.map, pos_margin: 0, position: "none" , width: "auto" });
|
||||
|
||||
this.slider.bind("time_clicked", function() {
|
||||
this.slider.toggleTime();
|
||||
}, this);
|
||||
|
||||
this.$el.find(".torque").append(this.slider.render().$el);
|
||||
|
||||
if (this.options.torqueLayer.hidden) this.slider.hide();
|
||||
else this.$el.addClass("with-torque");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_renderSlidesController: function() {
|
||||
|
||||
if (this.slides) {
|
||||
|
||||
this.$el.addClass("with-slides");
|
||||
|
||||
this.slidesController = new cdb.geo.ui.SlidesController({
|
||||
show_counter: true,
|
||||
transitions: this.options.transitions,
|
||||
visualization: this.options.visualization,
|
||||
slides: this.slides
|
||||
});
|
||||
|
||||
this.$el.append(this.slidesController.render().$el);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
this._bindOrientationChange();
|
||||
|
||||
this.$el.html(this.template(this.options));
|
||||
|
||||
this.$header = this.$el.find(".cartodb-header");
|
||||
this.$header.show();
|
||||
|
||||
this._renderOverlays();
|
||||
|
||||
this._renderSlidesController();
|
||||
|
||||
this._addAttributions();
|
||||
|
||||
this._getLayers();
|
||||
this._renderLayers();
|
||||
this._renderTorque();
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* UI component to place the map in the
|
||||
* location found by the geocoder.
|
||||
*
|
||||
*/
|
||||
|
||||
cdb.geo.ui.Search = cdb.core.View.extend({
|
||||
|
||||
className: 'cartodb-searchbox',
|
||||
|
||||
GEOCODER_SERVICE: 'TOMTOM',
|
||||
|
||||
_ZOOM_BY_CATEGORY: {
|
||||
'building': 18,
|
||||
'postal-area': 15,
|
||||
'venue':18,
|
||||
'region':8,
|
||||
'address':18,
|
||||
'country':5,
|
||||
'county':8,
|
||||
'locality':12,
|
||||
'localadmin':11,
|
||||
'neighbourhood':15,
|
||||
'default': 12
|
||||
},
|
||||
|
||||
events: {
|
||||
"click input[type='text']": '_onFocus',
|
||||
"submit form": '_onSubmit',
|
||||
"click": '_stopPropagation',
|
||||
"dblclick": '_stopPropagation',
|
||||
"mousedown": '_stopPropagation'
|
||||
},
|
||||
|
||||
options: {
|
||||
searchPin: true,
|
||||
infowindowTemplate: '<div class="cartodb-infowindow">'+
|
||||
'<div class="cartodb-popup v2 centered">'+
|
||||
'<a href="#close" class="cartodb-popup-close-button close">x</a>'+
|
||||
'<div class="cartodb-popup-content-wrapper">'+
|
||||
'<p>{{ address }}</p>'+
|
||||
'</div>'+
|
||||
'<div class="cartodb-popup-tip-container"></div>'+
|
||||
'</div>',
|
||||
infowindowWidth: 186,
|
||||
infowindowOffset: [93, 90],
|
||||
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.mapView = this.options.mapView;
|
||||
this.template = this.options.template;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template(this.options));
|
||||
return this;
|
||||
},
|
||||
|
||||
_stopPropagation: function(ev) {
|
||||
if (ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
_onFocus: function(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
$(ev.target).focus();
|
||||
}
|
||||
},
|
||||
|
||||
_showLoader: function() {
|
||||
this.$('span.loader').show();
|
||||
},
|
||||
|
||||
_hideLoader: function() {
|
||||
this.$('span.loader').hide();
|
||||
},
|
||||
|
||||
_onSubmit: function(ev) {
|
||||
ev.preventDefault();
|
||||
var self = this;
|
||||
var address = this.$('input.text').val();
|
||||
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show geocoder loader
|
||||
this._showLoader();
|
||||
// Remove previous pin
|
||||
this._destroySearchPin();
|
||||
cdb.geo.geocoder[this.GEOCODER_SERVICE].geocode(address, function(places) {
|
||||
self._onResult(places);
|
||||
// Hide loader
|
||||
self._hideLoader();
|
||||
});
|
||||
},
|
||||
|
||||
_onResult: function(places) {
|
||||
var position = '';
|
||||
var address = this.$('input.text').val();
|
||||
|
||||
if (places && places.length>0) {
|
||||
var location = places[0];
|
||||
var validBBox = this._isBBoxValid(location);
|
||||
|
||||
// Get BBox if possible and set bounds
|
||||
if (validBBox) {
|
||||
var s = parseFloat(location.boundingbox.south);
|
||||
var w = parseFloat(location.boundingbox.west);
|
||||
var n = parseFloat(location.boundingbox.north);
|
||||
var e = parseFloat(location.boundingbox.east);
|
||||
|
||||
var centerLon = (w + e)/2;
|
||||
var centerLat = (s + n)/2;
|
||||
position = [centerLat, centerLon];
|
||||
this.model.setBounds([ [ s, w ], [ n, e ] ]);
|
||||
}
|
||||
|
||||
// If location is defined,
|
||||
// let's store it
|
||||
if (location.lat && location.lon) {
|
||||
position = [location.lat, location.lon];
|
||||
}
|
||||
|
||||
// In the case that BBox is not valid, let's
|
||||
// center the map using the position
|
||||
if (!validBBox) {
|
||||
this.model.setCenter(position);
|
||||
this.model.setZoom(this._getZoomByCategory(location.type));
|
||||
}
|
||||
|
||||
if (this.options.searchPin) {
|
||||
this._createSearchPin(position, address);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 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'];
|
||||
},
|
||||
|
||||
_isBBoxValid: function(location) {
|
||||
if(!location.boundingbox || location.boundingbox.south == location.boundingbox.north ||
|
||||
location.boundingbox.east == location.boundingbox.west) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_createSearchPin: function(position, address) {
|
||||
this._destroySearchPin();
|
||||
this._createPin(position, address);
|
||||
this._createInfowindow(position, address);
|
||||
this._bindEvents();
|
||||
},
|
||||
|
||||
_destroySearchPin: function() {
|
||||
this._unbindEvents();
|
||||
this._destroyPin();
|
||||
this._destroyInfowindow();
|
||||
},
|
||||
|
||||
_createInfowindow: function(position, address) {
|
||||
var infowindowModel = new cdb.geo.ui.InfowindowModel({
|
||||
template: this.options.infowindowTemplate,
|
||||
latlng: position,
|
||||
width: this.options.infowindowWidth,
|
||||
offset: this.options.infowindowOffset,
|
||||
content: {
|
||||
fields: [{
|
||||
title: 'address',
|
||||
value: address
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
this._searchInfowindow = new cdb.geo.ui.Infowindow({
|
||||
model: infowindowModel,
|
||||
mapView: this.mapView
|
||||
});
|
||||
|
||||
this.mapView.$el.append(this._searchInfowindow.el);
|
||||
infowindowModel.set('visibility', true);
|
||||
},
|
||||
|
||||
_destroyInfowindow: function() {
|
||||
if (this._searchInfowindow) {
|
||||
// Hide it and then destroy it (when animation ends)
|
||||
this._searchInfowindow.hide(true);
|
||||
var infowindow = this._searchInfowindow;
|
||||
setTimeout(function() {
|
||||
infowindow.clean();
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
_createPin: function(position, address) {
|
||||
this._searchPin = this.mapView._addGeomToMap(
|
||||
new cdb.geo.Geometry({
|
||||
geojson: { type: "Point", "coordinates": [ position[1], position[0] ] },
|
||||
iconUrl: this.options.iconUrl,
|
||||
iconAnchor: this.options.iconAnchor
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_toggleSearchInfowindow: function() {
|
||||
var infowindowVisibility = this._searchInfowindow.model.get('visibility');
|
||||
this._searchInfowindow.model.set('visibility', !infowindowVisibility);
|
||||
},
|
||||
|
||||
_destroyPin: function() {
|
||||
if (this._searchPin) {
|
||||
this.mapView._removeGeomFromMap(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);
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
this._unbindEvents();
|
||||
this._destroySearchPin();
|
||||
this.elder('clean');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
cdb.geo.ui.Share = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-share",
|
||||
|
||||
events: {
|
||||
"click a": "_onClick"
|
||||
},
|
||||
default_options: { },
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.bindAll(this, "_onClick");
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.template = this.options.template;
|
||||
|
||||
},
|
||||
|
||||
_applyStyle: function() { },
|
||||
|
||||
_onClick: function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dialog.show();
|
||||
|
||||
},
|
||||
|
||||
createDialog: function() {
|
||||
|
||||
var data = this.options;
|
||||
data.template = "";
|
||||
|
||||
// Add the complete url for facebook and twitter
|
||||
if (location.href) {
|
||||
data.share_url = encodeURIComponent(location.href);
|
||||
} else {
|
||||
data.share_url = data.url;
|
||||
}
|
||||
|
||||
var template = cdb.core.Template.compile(
|
||||
data.template || '\
|
||||
<div class="mamufas">\
|
||||
<div class="block modal {{modal_type}}">\
|
||||
<a href="#close" class="close">x</a>\
|
||||
<div class="head">\
|
||||
<h3>Share this map</h3>\
|
||||
</div>\
|
||||
<div class="content">\
|
||||
<div class="buttons">\
|
||||
<h4>Social</h4>\
|
||||
<ul>\
|
||||
<li><a class="facebook" target="_blank" href="{{ facebook_url }}">Share on Facebook</a></li>\
|
||||
<li><a class="twitter" href="{{ twitter_url }}" target="_blank">Share on Twitter</a></li>\
|
||||
<li><a class="link" href="{{ public_map_url }}" target="_blank">Link to this map</a></li>\
|
||||
</ul>\
|
||||
</div><div class="embed_code">\
|
||||
<h4>Embed this map</h4>\
|
||||
<textarea id="" name="" cols="30" rows="10">{{ code }}</textarea>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
',
|
||||
data.templateType || 'mustache'
|
||||
);
|
||||
|
||||
var url = location.href;
|
||||
|
||||
url = url.replace("public_map", "embed_map");
|
||||
|
||||
var public_map_url = url.replace("embed_map", "public_map"); // TODO: get real URL
|
||||
|
||||
var code = "<iframe width='100%' height='520' frameborder='0' src='" + url + "' allowfullscreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen></iframe>";
|
||||
|
||||
this.dialog = new cdb.ui.common.ShareDialog({
|
||||
title: data.map.get("title"),
|
||||
description: data.map.get("description"),
|
||||
model: this.options.vis.map,
|
||||
code: code,
|
||||
url: data.url,
|
||||
public_map_url: public_map_url,
|
||||
share_url: data.share_url,
|
||||
template: template,
|
||||
target: $(".cartodb-share a"),
|
||||
size: $(document).width() > 400 ? "" : "small",
|
||||
width: $(document).width() > 400 ? 430 : 216
|
||||
});
|
||||
|
||||
$(".cartodb-map-wrapper").append(this.dialog.render().$el);
|
||||
|
||||
this.addView(this.dialog);
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
this.$el.html(this.template(_.extend(this.model.attributes)));
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
cdb.geo.ui.SlidesControllerItem = cdb.core.View.extend({
|
||||
|
||||
tagName: "li",
|
||||
|
||||
events: {
|
||||
"click a": "_onClick",
|
||||
},
|
||||
|
||||
template: cdb.core.Template.compile('<a href="#" class="<%- transition_trigger %>"></a>'),
|
||||
|
||||
initialize: function() {
|
||||
|
||||
this.model = new cdb.core.Model(this.options);
|
||||
this.model.bind("change:active", this._onChangeActive, this);
|
||||
|
||||
},
|
||||
|
||||
_onChangeActive: function(e) {
|
||||
|
||||
if (this.model.get("active")) {
|
||||
this.$el.find("a").addClass("active");
|
||||
} else {
|
||||
this.$el.find("a").removeClass("active");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_onClick: function(e) {
|
||||
if (e) this.killEvent(e);
|
||||
this.trigger("onClick", this)
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
var options = _.extend({ transition_trigger: "click" }, this.options.transition_options);
|
||||
|
||||
this.$el.html(this.template(options));
|
||||
|
||||
this._onChangeActive();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
cdb.geo.ui.SlidesController = cdb.core.View.extend({
|
||||
|
||||
defaults: {
|
||||
show_counter: false
|
||||
},
|
||||
|
||||
events: {
|
||||
'click a.next': "_next",
|
||||
'click a.prev': "_prev"
|
||||
},
|
||||
|
||||
tagName: "div",
|
||||
|
||||
className: "cartodb-slides-controller",
|
||||
|
||||
template: cdb.core.Template.compile("<div class='slides-controller-content'><a href='#' class='prev'></a><% if (show_counter) {%><div class='counter'></div><% } else { %><ul></ul><% } %><a href='#' class='next'></a></div>"),
|
||||
|
||||
initialize: function() {
|
||||
this.slidesCount = this.options.transitions.length;
|
||||
this.visualization = this.options.visualization;
|
||||
this.slides = this.visualization.slides;
|
||||
},
|
||||
|
||||
_prev: function(e) {
|
||||
if (e) this.killEvent(e);
|
||||
this.visualization.sequence.prev();
|
||||
},
|
||||
|
||||
_next: function(e) {
|
||||
if (e) this.killEvent(e);
|
||||
this.visualization.sequence.next();
|
||||
},
|
||||
|
||||
_renderDots: function() {
|
||||
|
||||
var currentActiveSlide = this.slides.state();
|
||||
|
||||
for (var i = 0; i < this.options.transitions.length; i++) {
|
||||
var item = new cdb.geo.ui.SlidesControllerItem({ num: i, transition_options: this.options.transitions[i], active: i == currentActiveSlide });
|
||||
item.bind("onClick", this._onSlideClick, this);
|
||||
this.$el.find("ul").append(item.render().$el);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_renderCounter: function() {
|
||||
|
||||
var currentActiveSlide = this.slides.state();
|
||||
var currentTransition = this.options.transitions[currentActiveSlide];
|
||||
|
||||
var $counter = this.$el.find(".counter");
|
||||
|
||||
if (currentTransition && currentTransition.transition_trigger === "time") {
|
||||
$counter.addClass("loading");
|
||||
} else {
|
||||
$counter.removeClass("loading");
|
||||
}
|
||||
|
||||
$counter.html((currentActiveSlide + 1) + "/" + this.options.transitions.length)
|
||||
},
|
||||
|
||||
_onSlideClick: function(slide) {
|
||||
this.visualization.sequence.current(slide.options.num);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
var options = _.extend(this.defaults, this.options);
|
||||
|
||||
this.$el.html(this.template(options));
|
||||
|
||||
if (this.slides && this.options.transitions) {
|
||||
|
||||
if (options.show_counter) {
|
||||
this._renderCounter(); // we render: 1/N
|
||||
} else {
|
||||
this._renderDots(); // we render a list of dots
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
cdb.geo.ui.SwitcherItemModel = Backbone.Model.extend({ });
|
||||
|
||||
cdb.geo.ui.SwitcherItems = Backbone.Collection.extend({
|
||||
model: cdb.geo.ui.SwitcherItemModel
|
||||
});
|
||||
|
||||
cdb.geo.ui.SwitcherItem = cdb.core.View.extend({
|
||||
|
||||
tagName: "li",
|
||||
|
||||
events: {
|
||||
|
||||
"click a" : "select"
|
||||
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.bindAll(this, "render");
|
||||
this.template = cdb.templates.getTemplate('templates/map/switcher/item');
|
||||
this.parent = this.options.parent;
|
||||
this.model.on("change:selected", this.render);
|
||||
|
||||
},
|
||||
|
||||
select: function(e) {
|
||||
e.preventDefault();
|
||||
this.parent.toggle(this);
|
||||
var callback = this.model.get("callback");
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
if (this.model.get("selected") == true) {
|
||||
this.$el.addClass("selected");
|
||||
} else {
|
||||
this.$el.removeClass("selected");
|
||||
}
|
||||
|
||||
this.$el.html(this.template(this.model.toJSON()));
|
||||
return this.$el;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
cdb.geo.ui.Switcher = cdb.core.View.extend({
|
||||
|
||||
id: "switcher",
|
||||
|
||||
default_options: {
|
||||
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
this.map = this.model;
|
||||
|
||||
this.add_related_model(this.model);
|
||||
|
||||
_.bindAll(this, "render", "show", "hide", "toggle");
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
if (this.collection) {
|
||||
this.model.collection = this.collection;
|
||||
}
|
||||
|
||||
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/switcher');
|
||||
},
|
||||
|
||||
show: function() {
|
||||
this.$el.fadeIn(250);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.$el.fadeOut(250);
|
||||
},
|
||||
|
||||
toggle: function(clickedItem) {
|
||||
|
||||
if (this.collection) {
|
||||
this.collection.each(function(item) {
|
||||
item.set("selected", !item.get("selected"));
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
|
||||
if (this.model != undefined) {
|
||||
this.$el.html(this.template(this.model.toJSON()));
|
||||
}
|
||||
|
||||
if (this.collection) {
|
||||
|
||||
this.collection.each(function(item) {
|
||||
|
||||
var view = new cdb.geo.ui.SwitcherItem({ parent: self, className: item.get("className"), model: item });
|
||||
self.$el.find("ul").append(view.render());
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
cdb.geo.ui.Text = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-overlay overlay-text",
|
||||
|
||||
events: {
|
||||
"click": "stopPropagation"
|
||||
},
|
||||
|
||||
default_options: { },
|
||||
|
||||
stopPropagation: function(e) {
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.template = this.options.template;
|
||||
|
||||
var self = this;
|
||||
|
||||
$(window).on("map_resized", function() {
|
||||
self._place();
|
||||
});
|
||||
|
||||
$(window).on("resize", function() {
|
||||
self._place();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
_applyStyle: function() {
|
||||
|
||||
var style = this.model.get("style");
|
||||
|
||||
var boxColor = style["box-color"];
|
||||
var boxOpacity = style["box-opacity"];
|
||||
var boxWidth = style["box-width"];
|
||||
var fontFamily = style["font-family-name"];
|
||||
|
||||
this.$text = this.$el.find(".text");
|
||||
|
||||
this.$text.css(style);
|
||||
this.$text.css("font-size", style["font-size"] + "px");
|
||||
|
||||
this.$el.css("z-index", style["z-index"]);
|
||||
|
||||
var fontFamilyClass = "";
|
||||
|
||||
if (fontFamily == "Droid Sans") fontFamilyClass = "droid";
|
||||
else if (fontFamily == "Vollkorn") fontFamilyClass = "vollkorn";
|
||||
else if (fontFamily == "Open Sans") fontFamilyClass = "open_sans";
|
||||
else if (fontFamily == "Roboto") fontFamilyClass = "roboto";
|
||||
else if (fontFamily == "Lato") fontFamilyClass = "lato";
|
||||
else if (fontFamily == "Graduate") fontFamilyClass = "graduate";
|
||||
else if (fontFamily == "Gravitas One") fontFamilyClass = "gravitas_one";
|
||||
else if (fontFamily == "Old Standard TT") fontFamilyClass = "old_standard_tt";
|
||||
|
||||
var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
|
||||
+ ',' + parseInt(boxColor.slice(-4,-2),16)
|
||||
+ ',' + parseInt(boxColor.slice(-2),16)
|
||||
+', ' + boxOpacity + ' )';
|
||||
|
||||
this.$el
|
||||
.removeClass("droid")
|
||||
.removeClass("vollkorn")
|
||||
.removeClass("roboto")
|
||||
.removeClass("open_sans")
|
||||
.removeClass("lato")
|
||||
.removeClass("graduate")
|
||||
.removeClass("gravitas_one")
|
||||
.removeClass("old_standard_tt");
|
||||
|
||||
this.$el.addClass(fontFamilyClass);
|
||||
this.$el.css({
|
||||
backgroundColor: rgbaCol,
|
||||
maxWidth: boxWidth
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
_place: function(position) {
|
||||
|
||||
var extra = position || this.model.get("extra");
|
||||
|
||||
var top = this.model.get("y");
|
||||
var left = this.model.get("x");
|
||||
|
||||
var bottom_position = extra.bottom - this.$el.height();
|
||||
var right_position = extra.right - this.$el.width();
|
||||
|
||||
// position percentages
|
||||
var top_percentage = extra.top_percentage;
|
||||
var left_percentage = extra.left_percentage;
|
||||
|
||||
var right = "auto";
|
||||
var bottom = "auto";
|
||||
|
||||
var marginTop = 0;
|
||||
var marginLeft = 0;
|
||||
|
||||
var width = extra.width;
|
||||
var height = extra.height;
|
||||
|
||||
var portrait_dominant_side = extra.portrait_dominant_side;
|
||||
var landscape_dominant_side = extra.landscape_dominant_side;
|
||||
|
||||
if (portrait_dominant_side === 'bottom' && bottom_position <= 250) {
|
||||
|
||||
top = "auto";
|
||||
bottom = bottom_position;
|
||||
|
||||
} else if (top_percentage > 45 && top_percentage < 55) {
|
||||
|
||||
top = "50%";
|
||||
marginTop = -height/2;
|
||||
|
||||
}
|
||||
|
||||
if (landscape_dominant_side === 'right' && right_position <= 250) {
|
||||
|
||||
left = "auto";
|
||||
right = right_position;
|
||||
|
||||
} else if (left_percentage > 45 && left_percentage < 55) {
|
||||
|
||||
left = "50%";
|
||||
marginLeft = -width/2;
|
||||
|
||||
}
|
||||
|
||||
this.$el.css({
|
||||
marginLeft: marginLeft,
|
||||
marginTop: marginTop,
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
this.$el.fadeIn(150, function() {
|
||||
callback && callback();
|
||||
});
|
||||
},
|
||||
|
||||
hide: function(callback) {
|
||||
this.$el.fadeOut(150, function() {
|
||||
callback && callback();
|
||||
});
|
||||
},
|
||||
|
||||
_fixLinks: function() {
|
||||
|
||||
this.$el.find("a").each(function(i, link) {
|
||||
$(this).attr("target", "_top");
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var text = cdb.core.sanitize.html(this.model.get("extra").rendered_text, this.model.get('sanitizeText'));
|
||||
var data = _.chain(this.model.attributes).clone().extend({ text: text }).value();
|
||||
this.$el.html(this.template(data));
|
||||
|
||||
this._fixLinks();
|
||||
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self._applyStyle();
|
||||
self._place();
|
||||
self.show();
|
||||
}, 900);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Show or hide tiles loader
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* var tiles_loader = new cdb.geo.ui.TilesLoader();
|
||||
* mapWrapper.$el.append(tiles_loader.render().$el);
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
cdb.geo.ui.TilesLoader = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-tiles-loader",
|
||||
|
||||
default_options: {
|
||||
animationSpeed: 500
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
_.defaults(this.options, this.default_options);
|
||||
this.isVisible = 0;
|
||||
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/tiles_loader');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html($(this.template(this.options)));
|
||||
return this;
|
||||
},
|
||||
|
||||
show: function(ev) {
|
||||
if(this.isVisible) return;
|
||||
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.util.browser.ie.version >= 10)) {
|
||||
this.$el.fadeTo(this.options.animationSpeed, 1)
|
||||
} else {
|
||||
this.$el.show();
|
||||
}
|
||||
this.isVisible++;
|
||||
},
|
||||
|
||||
hide: function(ev) {
|
||||
this.isVisible--;
|
||||
if(this.isVisible > 0) return;
|
||||
this.isVisible = 0;
|
||||
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.util.browser.ie.version >= 10)) {
|
||||
this.$el.stop(true).fadeTo(this.options.animationSpeed, 0)
|
||||
} else {
|
||||
this.$el.hide();
|
||||
}
|
||||
},
|
||||
|
||||
visible: function() {
|
||||
return this.isVisible > 0;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,231 @@
|
||||
cdb.geo.ui.TimeSlider = cdb.geo.ui.InfoBox.extend({
|
||||
|
||||
DEFAULT_OFFSET_TOP: 30,
|
||||
className: 'cartodb-timeslider',
|
||||
|
||||
defaultTemplate:
|
||||
" <ul> " +
|
||||
" <li class='controls'><a href='#/stop' class='button stop'>pause</a></li>" +
|
||||
" <li class='time'><p class='value'></p></li>" +
|
||||
" <li class='last'><div class='slider-wrapper'><div class='slider'></div></div></li>" +
|
||||
" </ul> "
|
||||
,
|
||||
|
||||
events: {
|
||||
"click .button": "toggleTime",
|
||||
"click .time": "_onClickTime",
|
||||
"dragstart": "_stopPropagation",
|
||||
"mousedown": "_stopPropagation",
|
||||
"touchstart": "_stopPropagation",
|
||||
"MSPointerDown": "_stopPropagation",
|
||||
"dblclick": "_stopPropagation",
|
||||
"mousewheel": "_stopPropagation",
|
||||
"DOMMouseScroll": "_stopPropagation",
|
||||
"click": "_stopPropagation"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
_.bindAll(this, '_stop', '_start', '_slide', '_bindLayer', '_unbindLayer', 'updateSliderRange', 'updateSlider', 'updateTime', 'toggleTime', 'toggleButton');
|
||||
var self = this;
|
||||
this.options.template = this.options.template || this.defaultTemplate;
|
||||
this.options.position = 'bottom|left';
|
||||
this.options.width = null;
|
||||
|
||||
// Control variable to know if the layer was
|
||||
// running before touching the slider
|
||||
this.wasRunning = false;
|
||||
|
||||
this._bindLayer(this.options.layer);
|
||||
|
||||
this.on('clean', this._unbindLayer);
|
||||
cdb.geo.ui.InfoBox.prototype.initialize.call(this);
|
||||
|
||||
},
|
||||
|
||||
setLayer: function(layer) {
|
||||
this._unbindLayer();
|
||||
this._bindLayer(layer);
|
||||
this._initSlider();
|
||||
},
|
||||
|
||||
_bindLayer: function(layer) {
|
||||
this.torqueLayer = layer;
|
||||
this.torqueLayer.on('change:time', this.updateSlider);
|
||||
this.torqueLayer.on('change:time', this.updateTime);
|
||||
this.torqueLayer.on('change:steps', this.updateSliderRange);
|
||||
this.torqueLayer.on('play', this.toggleButton);
|
||||
this.torqueLayer.on('pause', this.toggleButton);
|
||||
return this;
|
||||
},
|
||||
|
||||
_unbindLayer: function() {
|
||||
this.torqueLayer.off('change:time', this.updateSlider);
|
||||
this.torqueLayer.off('change:time', this.updateTime);
|
||||
this.torqueLayer.off('change:steps', this.updateSliderRange);
|
||||
return this;
|
||||
},
|
||||
|
||||
updateSlider: function(changes) {
|
||||
this.$(".slider" ).slider({ value: changes.step });
|
||||
},
|
||||
|
||||
updateSliderRange: function(changes) {
|
||||
if (changes.steps > 1){
|
||||
this.show();
|
||||
this.$(".slider" ).slider({ max: changes.steps - 1 });
|
||||
}
|
||||
else{
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
// each time time changes, move the slider
|
||||
updateTime: function(changes) {
|
||||
var self = this;
|
||||
var tb = self.torqueLayer.getTimeBounds();
|
||||
if (!tb) return;
|
||||
if (tb.columnType === 'date' || this.options.force_format_date) {
|
||||
if (tb && tb.start !== undefined) {
|
||||
var f = self.options.formatter || this.formatterForRange(tb.start, tb.end);
|
||||
// avoid showing invalid dates
|
||||
if (!_.isNaN(changes.time.getYear())) {
|
||||
self.$('.value').text(f(changes.time, this.torqueLayer));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.$('.value').text(changes.step);
|
||||
}
|
||||
},
|
||||
|
||||
formatter: function(_) {
|
||||
this.options.formatter = _;
|
||||
},
|
||||
|
||||
formatterForRange: function(start, end) {
|
||||
start = start.getTime ? start : new Date(start);
|
||||
end = end.getTime ? end : new Date(end);
|
||||
var range = (end.getTime() - start.getTime()) / 1000;
|
||||
var ONE_DAY = 3600*24;
|
||||
var THREE_DAYS = ONE_DAY*3;
|
||||
var ONE_YEAR = ONE_DAY * 31 * 12;
|
||||
if(this.torqueLayer.options){
|
||||
var stepDurationMS = (end.getTime() - start.getTime()) / this.torqueLayer.options.steps;
|
||||
}
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
function toUSDateStr(date) {
|
||||
return pad(date.getMonth() + 1) + "/" + pad(date.getDate()) + "/" + pad(date.getFullYear());
|
||||
}
|
||||
|
||||
function toTimeStr(date) {
|
||||
return pad(date.getHours()) + ":" + pad(date.getMinutes());
|
||||
}
|
||||
|
||||
function toDateRange(date, torqueLayer) {
|
||||
var stepStartTimeMS = date.getTime();
|
||||
var tb = torqueLayer.getTimeBounds();
|
||||
var stepDurationMS = (new Date(tb.end).getTime() - new Date(tb.start).getTime()) / torqueLayer.options.steps;
|
||||
var stepEndTime = new Date(stepStartTimeMS + stepDurationMS);
|
||||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
return pad(months[date.getMonth()]) + " " + pad(date.getDate()) + " - "
|
||||
+ pad(months[stepEndTime.getMonth()]) + " " + pad(stepEndTime.getDate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (range < THREE_DAYS) {
|
||||
if (start.getDate() === end.getDate()) {
|
||||
return toTimeStr;
|
||||
} else {
|
||||
// range is less than a day, but the range spans more than one day so render the date in addition to the time
|
||||
return function(date) {
|
||||
return toUSDateStr(date) +' '+ toTimeStr(date);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (range < ONE_YEAR) {
|
||||
if (stepDurationMS > ONE_DAY * 2000){ // More than 48 hours
|
||||
return toDateRange;
|
||||
}
|
||||
return toUSDateStr;
|
||||
}
|
||||
|
||||
// >= ONE_YEAR
|
||||
return function(date) {
|
||||
return pad(date.getMonth() + 1) + "/" + pad(date.getFullYear());
|
||||
};
|
||||
},
|
||||
|
||||
_slide: function(e, ui) {
|
||||
this.killEvent(e);
|
||||
var step = ui.value;
|
||||
this.torqueLayer.setStep(step);
|
||||
},
|
||||
|
||||
_start: function(e, ui) {
|
||||
if(this.torqueLayer.isRunning()) {
|
||||
this.wasRunning = true;
|
||||
this.toggleTime();
|
||||
}
|
||||
},
|
||||
|
||||
_stop: function(e, ui) {
|
||||
if (this.wasRunning) {
|
||||
this.toggleTime()
|
||||
}
|
||||
|
||||
this.wasRunning = false;
|
||||
},
|
||||
|
||||
_initSlider: function() {
|
||||
var torqueLayer = this.torqueLayer;
|
||||
var slider = this.$(".slider");
|
||||
slider.slider({
|
||||
range: 'min',
|
||||
min: 0,
|
||||
max: this.torqueLayer.options.steps,
|
||||
value: 0,
|
||||
step: 1, //
|
||||
value: this.torqueLayer.getStep(),
|
||||
stop: this._stop,
|
||||
start: this._start,
|
||||
slide: this._slide
|
||||
});
|
||||
if(torqueLayer.provider){
|
||||
this.updateSliderRange({"steps": torqueLayer.provider.getSteps()});
|
||||
}
|
||||
},
|
||||
|
||||
toggleTime: function(e) {
|
||||
this.killEvent(e);
|
||||
this.torqueLayer.toggle();
|
||||
},
|
||||
toggleButton: function() {
|
||||
this.$('.button')
|
||||
[(this.torqueLayer.isRunning() ? 'addClass': 'removeClass')]('stop')
|
||||
.attr('href','#/' + (this.torqueLayer.isRunning() ? 'pause': 'play'))
|
||||
.html(this.torqueLayer.isRunning() ? 'pause': 'play');
|
||||
},
|
||||
|
||||
enable: function() {},
|
||||
|
||||
_stopPropagation: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
_onClickTime: function() {
|
||||
this.trigger("time_clicked", this);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
cdb.geo.ui.InfoBox.prototype.render.apply(this, arguments);
|
||||
this._initSlider();
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
|
||||
cdb.geo.ui.Tooltip = cdb.geo.ui.InfoBox.extend({
|
||||
|
||||
defaultTemplate: '<p>{{text}}</p>',
|
||||
className: 'cartodb-tooltip',
|
||||
|
||||
defaults: {
|
||||
vertical_offset: 0,
|
||||
horizontal_offset: 0,
|
||||
position: 'top|center'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
if(!this.options.mapView) {
|
||||
throw new Error("mapView should be present");
|
||||
}
|
||||
this.options.template = this.options.template || this.defaultTemplate;
|
||||
cdb.geo.ui.InfoBox.prototype.initialize.call(this);
|
||||
this._filter = null;
|
||||
this.showing = false;
|
||||
this.showhideTimeout = null;
|
||||
},
|
||||
|
||||
setLayer: function(layer) {
|
||||
this.options.layer = layer;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* sets a filter to open the tooltip. If the feature being hovered
|
||||
* pass the filter the tooltip is shown
|
||||
* setFilter(null) removes the filter
|
||||
*/
|
||||
setFilter: function(f) {
|
||||
this._filter = f;
|
||||
return this;
|
||||
},
|
||||
|
||||
setFields: function(fields) {
|
||||
this.options.fields = fields;
|
||||
return this;
|
||||
},
|
||||
|
||||
setAlternativeNames: function(n) {
|
||||
this.options.alternative_names = n;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
if(this.options.layer) {
|
||||
// unbind previous events
|
||||
this.options.layer.unbind(null, null, this);
|
||||
this.options.layer
|
||||
.on('mouseover', function(e, latlng, pos, data) {
|
||||
|
||||
if (this.options.fields && this.options.fields.length > 0) {
|
||||
|
||||
var non_valid_keys = ['fields', 'content'];
|
||||
|
||||
if (this.options.omit_columns) {
|
||||
non_valid_keys = non_valid_keys.concat(this.options.omit_columns);
|
||||
}
|
||||
|
||||
var c = cdb.geo.ui.InfowindowModel.contentForFields(data, this.options.fields, {
|
||||
empty_fields: this.options.empty_fields
|
||||
});
|
||||
|
||||
// Remove fields and content from data
|
||||
// and make them visible for custom templates
|
||||
data.content = _.omit(data, non_valid_keys);
|
||||
|
||||
// loop through content values
|
||||
data.fields = c.fields;
|
||||
|
||||
// alternamte names
|
||||
var names = this.options.alternative_names;
|
||||
if (names) {
|
||||
for(var i = 0; i < data.fields.length; ++i) {
|
||||
var f = data.fields[i];
|
||||
f.title = names[f.title] || f.title;
|
||||
}
|
||||
}
|
||||
this.show(pos, data);
|
||||
this.showing = true;
|
||||
} else if (this.showing) {
|
||||
this.hide();
|
||||
this.showing = false;
|
||||
}
|
||||
}, this)
|
||||
.on('mouseout', function() {
|
||||
if (this.showing) {
|
||||
this.hide();
|
||||
this.showing = false;
|
||||
}
|
||||
}, this);
|
||||
this.add_related_model(this.options.layer);
|
||||
}
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if(this.options.layer) {
|
||||
this.options.layer.unbind(null, null, this);
|
||||
}
|
||||
this.hide();
|
||||
this.showing = false;
|
||||
},
|
||||
|
||||
_visibility: function() {
|
||||
var self = this;
|
||||
clearTimeout(this.showhideTimeout);
|
||||
this.showhideTimeout = setTimeout(self._showing ?
|
||||
function() { self.$el.fadeIn(100); }
|
||||
:
|
||||
function() { self.$el.fadeOut(200); }
|
||||
, 50);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if (this._showing) {
|
||||
this._showing = false;
|
||||
this._visibility();
|
||||
}
|
||||
},
|
||||
|
||||
show: function(pos, data) {
|
||||
if (this._filter && !this._filter(data)) {
|
||||
return this;
|
||||
}
|
||||
this.render(data);
|
||||
//this.elder('show', pos, data);
|
||||
this.setPosition(pos);
|
||||
if (!this._showing) {
|
||||
this._showing = true;
|
||||
this._visibility();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setPosition: function(point) {
|
||||
var pos = this.options.position;
|
||||
var height = this.$el.innerHeight();
|
||||
var width = this.$el.innerWidth();
|
||||
var mapViewSize = this.options.mapView.getSize();
|
||||
var top = 0;
|
||||
var left = 0;
|
||||
|
||||
// Vertically
|
||||
if (pos.indexOf('top') !== -1) {
|
||||
top = point.y - height;
|
||||
} else if (pos.indexOf('middle') !== -1) {
|
||||
top = point.y - (height/2);
|
||||
} else { // bottom
|
||||
top = point.y;
|
||||
}
|
||||
|
||||
// Fix vertical overflow
|
||||
if (top < 0) {
|
||||
top = point.y;
|
||||
} else if (top + height > mapViewSize.y) {
|
||||
top = point.y - height;
|
||||
}
|
||||
|
||||
// Horizontally
|
||||
if(pos.indexOf('left') !== -1) {
|
||||
left = point.x - width;
|
||||
} else if(pos.indexOf('center') !== -1) {
|
||||
left = point.x - (width/2);
|
||||
} else { // right
|
||||
left = point.x;
|
||||
}
|
||||
|
||||
// Fix horizontal overflow
|
||||
if (left < 0) {
|
||||
left = point.x;
|
||||
} else if (left + width > mapViewSize.x) {
|
||||
left = point.x - width;
|
||||
}
|
||||
|
||||
// Add offsets
|
||||
top += this.options.vertical_offset;
|
||||
left += this.options.horizontal_offset;
|
||||
|
||||
this.$el.css({
|
||||
top: top,
|
||||
left: left
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var sanitizedOutput = cdb.core.sanitize.html(this.template(data));
|
||||
this.$el.html( sanitizedOutput );
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* View to control the zoom of the map.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* var zoomControl = new cdb.geo.ui.Zoom({ model: map });
|
||||
* mapWrapper.$el.append(zoomControl.render().$el);
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
cdb.geo.ui.Zoom = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-zoom",
|
||||
|
||||
events: {
|
||||
'click .zoom_in': 'zoom_in',
|
||||
'click .zoom_out': 'zoom_out'
|
||||
},
|
||||
|
||||
default_options: {
|
||||
timeout: 0,
|
||||
msg: ''
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.map = this.model;
|
||||
|
||||
_.defaults(this.options, this.default_options);
|
||||
|
||||
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/zoom');
|
||||
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.$('.zoom_in')[ zoom < this.map.get('maxZoom') ? 'removeClass' : 'addClass' ]('disabled')
|
||||
this.$('.zoom_out')[ zoom > this.map.get('minZoom') ? 'removeClass' : 'addClass' ]('disabled')
|
||||
},
|
||||
|
||||
zoom_in: function(ev) {
|
||||
if (this.map.get("maxZoom") > this.map.getZoom()) {
|
||||
this.map.setZoom(this.map.getZoom() + 1);
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
zoom_out: function(ev) {
|
||||
if (this.map.get("minZoom") < this.map.getZoom()) {
|
||||
this.map.setZoom(this.map.getZoom() - 1);
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* View to know which is the map zoom.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* var zoomInfo = new cdb.geo.ui.ZoomInfo({ model: map });
|
||||
* mapWrapper.$el.append(zoomInfo.render().$el);
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
cdb.geo.ui.ZoomInfo = cdb.core.View.extend({
|
||||
|
||||
className: "cartodb-zoom-info",
|
||||
|
||||
initialize: function() {
|
||||
this.model.bind("change:zoom", this.render, this);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.model.get("zoom"));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user