4354 lines
108 KiB
JavaScript
4354 lines
108 KiB
JavaScript
/*
|
|
* Timeglider for Javascript / jQuery
|
|
* http://timeglider.com/jquery
|
|
*
|
|
* Copyright 2013, Mnemograph LLC
|
|
* Licensed under Timeglider Dual License
|
|
* http://timeglider.com/jquery/?p=license
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************
|
|
timeglider.TimelineView
|
|
****************************************
|
|
*/
|
|
(function(tg){
|
|
|
|
// MED below is a reference to the mediator reference
|
|
// that will be passed into the main Constructor below
|
|
|
|
var TG_Date = tg.TG_Date,
|
|
PL = "",
|
|
MED = "",
|
|
options = {},
|
|
ticksSpeed = 0,
|
|
t1Left = 0,
|
|
t2Left = 0,
|
|
ticksSpeedIv,
|
|
container_name = '',
|
|
$ = jQuery,
|
|
intervals ={},
|
|
WIDGET_ID = "",
|
|
CONTAINER, TICKS, DATE, FOCUS_DATE,
|
|
CLICKORTOUCH = $.support.touch ? "touchstart": "click";
|
|
|
|
var stripPx = function (somethingPx) {
|
|
if (typeof somethingPx == "number") return somethingPx;
|
|
if (!somethingPx) return 0;
|
|
return parseInt(somethingPx.replace("px", ""), 10);
|
|
}
|
|
|
|
/*
|
|
* timeglider.TG_TimelineView
|
|
* This is _not_ a backbone view, though
|
|
* other elements inside of it are.
|
|
*
|
|
*
|
|
*/
|
|
tg.TG_TimelinePlayer = function (widget, mediator) {
|
|
|
|
var me = this;
|
|
|
|
// this.MED = mediator;
|
|
|
|
// vars declared in closure above
|
|
MED = mediator;
|
|
|
|
// timeline | list
|
|
MED.viewMode = "timeline";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
options = mediator.options;
|
|
|
|
// core identifier to "uniquify" the container
|
|
PL = "#" + widget._id;
|
|
|
|
WIDGET_ID = widget._id;
|
|
container_name = options.base_namespace + "#" + WIDGET_ID;
|
|
|
|
this.titleBar = true;
|
|
this.singleTitleHeight = 0;
|
|
|
|
MED.setImageLaneHeight(options.image_lane_height, false, true);
|
|
|
|
|
|
|
|
|
|
$("#test2").bind('click',function()
|
|
{
|
|
var date_str="2015-8-22 5:54:00";
|
|
MED.gotoDateZoom(date_str);
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* references specific to the instance (rather than timeglider) so
|
|
one can have more than one instance of the widget on a page */
|
|
this._views = {
|
|
PLACE:PL,
|
|
CONTAINER : PL + " .timeglider-container",
|
|
SCRIM : PL + " .tg-scrim",
|
|
DATE : PL + " .tg-footer-center",
|
|
HEADER : PL + " .tg-widget-header",
|
|
FOCUS_DATE : PL + " .tg-date-display",
|
|
TIMELINE_MENU : PL + " .timeglider-timeline-menu",
|
|
TIMELINE_MENU_UL : PL + " .timeglider-timeline-menu ul",
|
|
TIMELINE_LIST_BT : PL + " .timeglider-list-bt",
|
|
SLIDER_CONTAINER : PL + " .timeglider-slider-container",
|
|
SLIDER : PL + " .timeglider-slider",
|
|
ZOOM_DISPLAY : PL + " .timeglider-zoomlevel-display",
|
|
TRUCK : PL + " .timeglider-truck",
|
|
CENTERLINE : PL + " .timeglider-centerline",
|
|
TICKS : PL + " .timeglider-ticks",
|
|
HANDLE : PL + " .timeglider-handle",
|
|
FOOTER : PL + " .timeglider-footer",
|
|
FILTER_BT : PL + " .timeglider-filter-bt",
|
|
FILTER_BOX : PL + " .timeglider-filter-box",
|
|
SETTINGS_BT : PL + " .timeglider-settings-bt"
|
|
}
|
|
|
|
// shorthand for common elements
|
|
CONTAINER = this._views.CONTAINER;
|
|
TICKS = this._views.TICKS;
|
|
|
|
this.dragSpeed = 0;
|
|
this.tickNum = 0;
|
|
this.leftside = 0;
|
|
this.rightside = 0;
|
|
this.ticksHandleOffset = 0;
|
|
this.timeoout_id = 1;
|
|
this.sliderActive = false;
|
|
this.ztop = 1000;
|
|
this.filterBoxActivated = false;
|
|
|
|
|
|
// this needs to be less than or equal to
|
|
// timeglider.css value for .timeglider-tick
|
|
// height property
|
|
this.tick_height = 32;
|
|
|
|
|
|
// a state var for the left-right position of the timeline
|
|
// to help track whether the timeline is too far left/right
|
|
// DEPRECATED 12 March 2013
|
|
// in favor of draggable containment array
|
|
//this.dragScopeState = {state:"okay",pos:0};
|
|
|
|
|
|
/* TEMPLATES FOR THINGS LIKE MODAL WINDOWS
|
|
* events themselves are non-templated and rendered in TG_Org.js
|
|
* as there are too many on-the-fly style attributes etc, and
|
|
* the current theory is that templating would create lag
|
|
*
|
|
*
|
|
*/
|
|
// in case custom event_modal fails, we need this object to exist
|
|
this._templates = {}
|
|
|
|
this._templates = {
|
|
// allows for customized templates imported
|
|
test : "testola",
|
|
|
|
event_modal_small: "<div class='tg-modal timeglider-ev-modal ui-widget-content ${extra_class}' id='${id}_modal'>"
|
|
+ "<div class='tg-close-button tg-close-button-remove'></div>"
|
|
+ "<div class='dateline'>{{html dateline}}</div>"
|
|
+ "<h4 id='title'>${title}</h4>"
|
|
+ "<div class='tg-ev-modal-description jscroll'><p>{{html image}}{{html description}}</p></div>"
|
|
+ "<ul class='timeglider-ev-modal-links'>{{html links}}</ul>"
|
|
+ "</div>",
|
|
|
|
// For displaying an exterior page directly in the modal
|
|
event_modal_iframe: "<div class='tg-modal timeglider-ev-modal ui-widget-content tg-iframe-modal' id='${id}_modal'>"
|
|
+ "<div class='tg-close-button tg-close-button-remove'></div>"
|
|
+ "<div class='dateline'>{{html dateline}}</div>"
|
|
+ "<h4 id='title'>{{html title}}</h4>"
|
|
+ "<iframe frameborder='none' src='${link}'></iframe>"
|
|
+ "</div>",
|
|
|
|
// generated, appended on the fly, then removed
|
|
event_modal_full : $.template( null,
|
|
////////
|
|
"<div class='tg-modal tg-full_modal' id='ev_${id}_modal'>"
|
|
+ "<div class='tg-full_modal_scrim'></div>"
|
|
+ "<div class='tg-full_modal_panel'>"
|
|
+ "<div class='tg-close-button tg-full_modal_close'></div>"
|
|
+ "<div class='tg-full_modal_content'>"
|
|
|
|
+ "<div class='dateline'>{{html dateline}}</div>"
|
|
+ "<h4>${title}</h4><div class='tg-full_modal-vidimg'></div>"
|
|
+ "<div class='tg-full_modal-body'>"
|
|
+ "{{html image}}{{html description}}"
|
|
// + "<div id='insert'></div>"
|
|
+ "</div>"
|
|
+ "<div class='tg-full_modal-links'><ul>{{html links}}</ul></div>"
|
|
// end of modal
|
|
+ "</div>"),
|
|
|
|
|
|
// generated, appended on the fly, then removed
|
|
filter_modal : $.template( null,
|
|
"<div class='tg-modal timeglider-filter-box'>"+
|
|
"<div class='tg-close-button'></div>"+
|
|
"<h3>search/filter</h3>"+
|
|
"<div class='timeglider-menu-modal-content'>"+
|
|
"<div class='timeglider-formline'>"+
|
|
"<input placeholder='keyword(s)' type='text' class='timeglider-filter-search'></div>"+
|
|
"<div class='tg-filter-cbs'>"+
|
|
"<input type='checkbox' id='filter_t' checked><label for='filter_t'>title</label>"+
|
|
" <input type='checkbox' id='filter_d'><label for='filter_d'>description</label>"+
|
|
"</div>"+
|
|
"<div class='timeglider-formline filter-tags'>"+
|
|
"<input type='text' id='filter-tags' class='timeglider-filter-tags'>"+
|
|
"</div>"+
|
|
// "<div class='timeglider-formline'>hide: "+
|
|
// "<input type='text' class='timeglider-filter-exclude'></div>"+
|
|
"<ul class='buttons'>"+
|
|
"<li class='timeglider-filter-apply'>go</li>"+
|
|
"<li class='timeglider-filter-clear'>clear</li>"+
|
|
"</ul></div>"+
|
|
"<div class='tg-modal-corner tg-modal-corner-south'>"+
|
|
"</div>"),
|
|
|
|
timeline_list_modal : $.template( null,
|
|
"<div class='tg-modal timeglider-timeline-menu'>"+
|
|
"<div class='tg-close-button'></div>"+
|
|
"<h3>timelines</h3>"+
|
|
"<div class='timeglider-menu-modal-content'><ul></ul></div>"+
|
|
"<div class='tg-modal-corner tg-modal-corner-south'>"+
|
|
"</div>"),
|
|
|
|
settings_modal : $.template( null,
|
|
"<div class='tg-modal timeglider-settings-modal'>"+
|
|
"<div class='tg-close-button'></div>"+
|
|
"<h3>settings</h3>"+
|
|
"<div class='timeglider-menu-modal-content'>"+
|
|
"<div class='timeglider-settings-timezone'></div></div>"+
|
|
"<div class='tg-modal-corner tg-modal-corner-south'>"+
|
|
"</div>"),
|
|
|
|
legend_modal : $.template( null,
|
|
"<div class='timeglider-menu-modal tg-legend tg-display-none' id='${id}'>"+
|
|
"<div class='tg-close-button-small tg-legend-close'></div>"+
|
|
"<div class='timeglider-menu-modal-content'><ul id='${id}'>{{html legend_list}}</ul>"+
|
|
|
|
"<div class='tg-legend-controls'><span class='tg-legend-all'>all</span><span class='tg-legend-none'> | none</span></div>"+
|
|
"</div>"+
|
|
"</div>")
|
|
|
|
};
|
|
|
|
|
|
|
|
this.timelineInfoModal = Backbone.View.extend({
|
|
|
|
tagName: "div",
|
|
|
|
model:tg.TG_Timeline,
|
|
|
|
className: 'tg-modal tg-timeline-modal ui-widget-content',
|
|
|
|
events: {
|
|
"click .tg-close": "remove",
|
|
"click .tg-timeline-start": "timelineStart",
|
|
"click .tg-modal-tags": "openTagsInfo"
|
|
},
|
|
// "tags":{"mardigras":2,"chris":2,"arizona":2,"netscape":2,"flop":1},
|
|
template: function () {
|
|
|
|
var tags1 = "", tags_intro = "", tags2 = "", thtm = [];
|
|
|
|
var tl_tags = this.model.get("tags");
|
|
// if tags
|
|
if (_.size(tl_tags) > 0) {
|
|
tags1 = "<li class='tg-modal-tags'></li>";
|
|
|
|
_.each(tl_tags, function(val, key) {
|
|
thtm.push(key + " (" + val + ")");
|
|
});
|
|
|
|
tags_intro = "<p class='tags-intro'>Use the search tool (at lower right) to filter this timeline according to these tags:</p>";
|
|
|
|
tags2 = "<div class='tg-modal-tags-info'>" + tags_intro + thtm.join(", ") + "</div>"
|
|
}
|
|
return "<h4>${title}</h4>"
|
|
+ "<div class='tg-close tg-close-button'></div>"
|
|
+ "<div class='tg-timeline-description jscroll'>{{html description}}</div>"
|
|
+ "<ul>" + tags1 + "<li data-timeline_id='" + this.model.get("id") + "' class='tg-timeline-start'>start</li></ul>" + tags2
|
|
+ "<div class='tg-modal-corner tg-modal-corner-north'></div>";
|
|
|
|
},
|
|
|
|
openTagsInfo: function() {
|
|
var $ti = $(this.el).find(".tg-modal-tags-info");
|
|
|
|
if (!$ti.is(":visible")) {
|
|
$(this.el).find(".tg-modal-tags-info").slideDown();
|
|
} else {
|
|
$(this.el).find(".tg-modal-tags-info").slideUp();
|
|
}
|
|
},
|
|
|
|
timelineStart: function() {
|
|
|
|
MED.focusTimeline(this.model.get("id"));
|
|
this.remove();
|
|
},
|
|
|
|
initialize: function() {
|
|
// this.model.bind('change', this.render, this);
|
|
},
|
|
|
|
render: function() {
|
|
$(this.el).html($.tmpl(this.template(), this.model.attributes)).attr("id", this.model.get("id") + "_timelineInfoModal");
|
|
return this;
|
|
},
|
|
|
|
remove: function() {
|
|
$(this.el).fadeOut();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
this.presInfoModal = Backbone.View.extend({
|
|
|
|
tagName: "div",
|
|
|
|
model:tg.TG_Timeline,
|
|
|
|
className: 'tg-modal tg-timeline-modal tg-pres-modal ui-widget-content',
|
|
|
|
events: {
|
|
"click .tg-close": "remove",
|
|
"click .tg-pres-start": "presStart"
|
|
},
|
|
|
|
template: function () {
|
|
|
|
return "<div class='tg-timeline-description jscroll'>{{html description}}</div>"
|
|
+ "<ul><li class='tg-close'>close</li><li class='tg-pres-start'>start</li></ul>"
|
|
+ "<div class='tg-modal-corner tg-modal-corner-north'></div>";
|
|
|
|
},
|
|
|
|
presStart: function() {
|
|
var pres = MED.presentation;
|
|
MED.gotoDateZoom(pres.focus_date.dateStr, pres.initial_zoom);
|
|
},
|
|
|
|
render: function() {
|
|
$(this.el).html($.tmpl(this.template(), this.model)).attr("id", "presInfoModal");
|
|
return this;
|
|
},
|
|
|
|
remove: function() {
|
|
$(this.el).remove();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
this.datepicker = Backbone.View.extend({
|
|
|
|
tagName: "div",
|
|
|
|
className: 'tg-modal tg-datepicker',
|
|
|
|
events: {
|
|
"click .tg-close-button": "remove",
|
|
"click .goto-save": "gotoDate",
|
|
"keydown .dateinput": "doKeydown"
|
|
},
|
|
|
|
template: function () {
|
|
|
|
var focus = MED.getFocusDate();
|
|
var val = focus.dateStr.split(" ")[0];
|
|
|
|
return "<div class='tg-close-button'></div>"
|
|
+ "<h3>Go to...</h3>"
|
|
+ "<div class='timeglider-menu-modal-content'>"
|
|
+ "<div class='tg-dtinput-wrap' id='goto-wrap'> "
|
|
+ "<input class='mousetrap dateinput' type='text' id='goto' value='" + val + "'>"
|
|
+ "<div class='goto-save save button' id='goto-go'>go</div>"
|
|
+ "<div class='cal_icon'></div>"
|
|
+ "</div>"
|
|
+ "</div>";
|
|
|
|
},
|
|
|
|
doKeydown: function(e) {
|
|
switch(e.which) {
|
|
case 0: case 9: case 13:
|
|
this.gotoDate();
|
|
break;
|
|
}
|
|
},
|
|
|
|
gotoDate: function() {
|
|
var date_str = $(this.el).find("input.dateinput").val();
|
|
MED.gotoDateZoom(date_str);
|
|
this.remove();
|
|
},
|
|
|
|
render: function() {
|
|
$(this.el).html($.tmpl(this.template(), this.model)).attr("id", "datepickerModal");
|
|
|
|
$(this.el).find("#goto-wrap").timegliderDatePicker({
|
|
is_touch_device:false,
|
|
position:{
|
|
my: "left bottom",
|
|
at: "left top",
|
|
// skip instance; it will be defined by datePicker
|
|
collision:"none"
|
|
}
|
|
});
|
|
|
|
return this;
|
|
},
|
|
|
|
remove: function() {
|
|
// $(this.el).fadeOut();
|
|
$(this.el).remove();
|
|
me.datepickerOpen = false;
|
|
}
|
|
});
|
|
|
|
|
|
|
|
$(CONTAINER)
|
|
.delegate(".timeline-info-bt", CLICKORTOUCH, function () {
|
|
var id = $(this).data("timeline_id");
|
|
me.timelineModal(id);
|
|
})
|
|
.delegate(".tg-expcol-bt", CLICKORTOUCH, function () {
|
|
var id = $(this).data("timeline_id");
|
|
me.expandCollapseTimeline(id);
|
|
})
|
|
.delegate(".tg-invert-bt", CLICKORTOUCH, function () {
|
|
var id = $(this).data("timeline_id");
|
|
me.invertTimeline(id);
|
|
})
|
|
.delegate(".tg-legend-bt", CLICKORTOUCH, function () {
|
|
var id = $(this).data("timeline_id");
|
|
me.legendModal(id);
|
|
})
|
|
.delegate(".tg-close-button-remove", CLICKORTOUCH, function () {
|
|
$(this).parent().remove()
|
|
})
|
|
.delegate(".tg-full_modal_scrim, .tg-full_modal_close", CLICKORTOUCH, function () {
|
|
$(".tg-full_modal").remove();
|
|
})
|
|
.delegate(".tg-event-overflow", CLICKORTOUCH, function () {
|
|
MED.zoom(-1);
|
|
})
|
|
.delegate(".tg-event-overflow", "hover", function (e) {
|
|
|
|
// var evid = $(this).data("event_id");
|
|
|
|
|
|
//!TODO
|
|
// take id and focus to it, then zoom in until it's
|
|
// visible: then highlight and fade out highlight
|
|
})
|
|
|
|
|
|
.delegate(".tg-legend-close", CLICKORTOUCH, function () {
|
|
var $legend = $(CONTAINER + " .tg-legend");
|
|
$legend.fadeOut(300, function () { $legend.remove(); });
|
|
|
|
MED.setFilters({origin:"legend", icon: "all"}, []);
|
|
})
|
|
|
|
|
|
.delegate(".tg-legend-all", CLICKORTOUCH, function () {
|
|
|
|
var $panel = $(this).closest(".tg-legend");
|
|
|
|
var panel_id = $panel.attr("id");
|
|
var icons = [];
|
|
|
|
if (panel_id == "pres" || timeglider.mode == "presentation") {
|
|
var pres_legend = MED.presentation.legend;
|
|
icons = _.pluck(pres_legend, "icon");
|
|
|
|
} else {
|
|
var tl = MED.timelineCollection.get(panel_id);
|
|
icons = _.pluck(tl.get("legend"), "icon");
|
|
}
|
|
|
|
if (options.legend.type === "checkboxes") {
|
|
MED.setFilters({origin:"legend", icon: "provided"}, icons);
|
|
|
|
$(CONTAINER + " .tg-legend li").each(function () {
|
|
$(this).addClass("tg-legend-icon-selected");
|
|
});
|
|
|
|
} else {
|
|
MED.setFilters({origin:"legend", icon: "all"});
|
|
|
|
$(CONTAINER + " .tg-legend li").each(function () {
|
|
$(this).removeClass("tg-legend-icon-selected");
|
|
});
|
|
}
|
|
})
|
|
|
|
|
|
// optional none button
|
|
.delegate(".tg-legend-none", CLICKORTOUCH, function () {
|
|
$(CONTAINER + " .tg-legend li").each(function () {
|
|
$(this).removeClass("tg-legend-icon-selected");
|
|
});
|
|
|
|
MED.setFilters({origin:"legend", icon: "none"});
|
|
})
|
|
|
|
|
|
.delegate(".tg-timeline-start", CLICKORTOUCH, function() {
|
|
var tid = $(this).data("timeline_id");
|
|
MED.focusTimeline(tid);
|
|
})
|
|
.delegate(".tg-prev", CLICKORTOUCH, function() {
|
|
MED.gotoPreviousEvent(true);
|
|
})
|
|
.delegate(".tg-next", CLICKORTOUCH, function() {
|
|
MED.gotoNextEvent(true);
|
|
})
|
|
.delegate(".timeglider-tick", CLICKORTOUCH, function(e) {
|
|
var loc = MED.getDateFromOffset(e.pageX);
|
|
MED.gotoDateZoom(loc.dateStr);
|
|
})
|
|
.delegate(".tg-pres-start", CLICKORTOUCH, function() {
|
|
me.startPresentation();
|
|
})
|
|
.delegate(".pres-info-bt", CLICKORTOUCH, function () {
|
|
me.presentationModal();
|
|
})
|
|
.delegate(".tg-pres-header h2", CLICKORTOUCH, function () {
|
|
var tid = $(this).data("timeline_id");
|
|
if (tid == "primary") {
|
|
me.startPresentation();
|
|
me.presentationModal();
|
|
} else {
|
|
alert("Drill baby, drill!");
|
|
}
|
|
})
|
|
.delegate(".tg-title-prev-events", CLICKORTOUCH, function() {
|
|
MED.gotoPreviousEvent(true);
|
|
})
|
|
.delegate(".tg-title-next-events", CLICKORTOUCH, function() {
|
|
MED.gotoNextEvent(true);
|
|
})
|
|
|
|
|
|
|
|
.css("height", $(PL).height());
|
|
|
|
|
|
$(".tg-zoom-in").bind(CLICKORTOUCH, function() {
|
|
MED.zoom(-1);
|
|
});
|
|
|
|
|
|
$(".tg-zoom-out").bind(CLICKORTOUCH, function() {
|
|
MED.zoom(1);
|
|
});
|
|
|
|
$(this._views.FOCUS_DATE).bind(CLICKORTOUCH, function() {
|
|
me.datepickerModal();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
$(window).resize(_.throttle(function() {
|
|
MED.resize();
|
|
}, 700));
|
|
|
|
|
|
|
|
// END CONTAINER CHAIN
|
|
|
|
|
|
MED.base_font_size = options.base_font_size;
|
|
|
|
if (options.show_footer == false) {
|
|
$(this._views.FOOTER).css("display", "none");
|
|
}
|
|
|
|
this.dimensions = MED.dimensions = this.getWidgetDimensions();
|
|
|
|
|
|
// distance from bottom of container (not vertically from ticks)
|
|
// for timelines to be by default; but if a timeline has a "top" value,
|
|
// it's position will be set according to that
|
|
this.initTimelineVOffset = this.dimensions.container.height - (this.dimensions.footer.height + this.dimensions.tick.height + 18);
|
|
|
|
|
|
// INITIAL CONSTRUCTION
|
|
this.buildSlider();
|
|
|
|
this.setupScroller();
|
|
|
|
this.setPanButton($(".timeglider-pan-right"),-30);
|
|
this.setPanButton($(".timeglider-pan-left"),30);
|
|
|
|
$(this._views.TRUCK)
|
|
|
|
// doubleclicking will be used by authoring mode
|
|
.bind('dblclick', function(e) {
|
|
MED.registerUIEvent({name:"dblclick", event:e});
|
|
})
|
|
|
|
|
|
.bind('mousewheel', function(event, delta) {
|
|
|
|
|
|
|
|
var vec = (delta < 0) ? Math.floor(delta): Math.ceil(delta);
|
|
var dir = -1 * vec;
|
|
MED.mousewheelChange(dir);
|
|
|
|
if (options.mousewheel !== "none") {
|
|
// prevent default browser scrolling
|
|
return false;
|
|
}
|
|
|
|
|
|
}); // end TRUCK EVENTS
|
|
|
|
|
|
function registerTicksSpeed () {
|
|
//!TODO: for gliding
|
|
}
|
|
|
|
|
|
|
|
$(TICKS)
|
|
.draggable({ axis: 'x',
|
|
|
|
start: function(event, ui) {
|
|
me.eventUnHover();
|
|
},
|
|
|
|
// will be overridden later
|
|
containment: [-20000,0,20000,0],
|
|
|
|
cancel:".tg-modal",
|
|
|
|
drag: function(event, ui) {
|
|
|
|
t1Left = Math.floor($(this).position().left);
|
|
|
|
MED.setTicksOffset(t1Left);
|
|
|
|
ticksSpeed = t1Left - t2Left;
|
|
|
|
t2Left = t1Left;
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
stop: function(event, ui) {
|
|
|
|
me.resetTicksHandle();
|
|
me.registerDragging();
|
|
me.registerTitles();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: make function displayCenterline()
|
|
// TODO: simply append a centerline template rather than .css'ing it!
|
|
me.resizeElements();
|
|
|
|
|
|
/* PUB-SUB "LISTENERS" SUBSCRIBERS */
|
|
|
|
$.subscribe(container_name + ".mediator.ticksOffsetChange", function () {
|
|
|
|
me.tickHangies();
|
|
me.registerDragging();
|
|
me.registerTitles();
|
|
|
|
});
|
|
|
|
$.subscribe(container_name + ".viewer.rendered", function () {
|
|
|
|
// change scroller
|
|
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.mousewheelChange", function (info) {
|
|
|
|
if (info.action === "pan") {
|
|
me.pan(info.dir * 10);
|
|
me.throttledResetTicksHandle();
|
|
}
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.focusToEvent", function () {
|
|
// mediator takes care of focusing date
|
|
var ev = MED.focusedEvent;
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.imageLaneHeightSetUi", function () {
|
|
me.setImageLaneHandle();
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.zoomLevelChange", function () {
|
|
|
|
me.tickNum = 0;
|
|
me.leftside = 0;
|
|
|
|
var zl = MED.getZoomLevel();
|
|
|
|
// if the slider isn't already at the given value change in
|
|
$(me._views.SLIDER).slider("value", me.invSliderVal(zl));
|
|
|
|
me.displayZoomLevel(zl);
|
|
|
|
me.castTicks("zoomLevelChange");
|
|
|
|
});
|
|
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.initialScope", function () {
|
|
|
|
var iscope = MED.getInitialScope(),
|
|
|
|
ls = iscope.left_sec,
|
|
rs = iscope.right_sec,
|
|
|
|
spp = iscope.spp,
|
|
c_pos = $(CONTAINER).offset(),
|
|
|
|
c_width = $(CONTAINER).width(),
|
|
|
|
lshift = ls - iscope.timelineBounds.first,
|
|
rshift = iscope.timelineBounds.last - rs,
|
|
|
|
// how far to drag right??
|
|
r_extreme = (Math.floor(lshift/spp) + c_pos.left) + (c_width/2),
|
|
// how far to drag left??;
|
|
l_extreme = (( -1 * Math.ceil(rshift/spp)) + c_pos.left) - (c_width/2);
|
|
|
|
if (options.constrain_to_data && MED.activeTimelines.length > 0) {
|
|
$(TICKS).draggable("option", "containment", [l_extreme, 0, r_extreme, 0]);
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/// This happens on a TOTAL REFRESH of
|
|
/// ticks, as when zooming; panning will load
|
|
/// events of active timelines per tick
|
|
$.subscribe(container_name + ".mediator.ticksReadySignal", function (b) {
|
|
if (MED.ticksReady === true) {
|
|
me.freshTimelines();
|
|
}
|
|
});
|
|
|
|
|
|
/*
|
|
Renews the timeline at current focus/zoom, but with
|
|
possibly different timeline/legend/etc parameters
|
|
! The only view method that responds directly to a model refresh()
|
|
*/
|
|
$.subscribe(container_name + ".mediator.refreshSignal", function () {
|
|
|
|
me.tickNum = 0;
|
|
me.leftside = 0;
|
|
|
|
me.castTicks("refreshSignal");
|
|
});
|
|
|
|
|
|
// adding to or removing from ticksArray
|
|
// DORMANT: necessary?
|
|
$.subscribe(container_name + ".mediator.ticksArrayChange", function () {
|
|
// empty for now
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.scopeChange", function() {
|
|
|
|
var scope = MED.getScope();
|
|
var tbounds = scope.timelineBounds;
|
|
var focus = scope.focusDateSec;
|
|
|
|
/*
|
|
// DEPRECATED 12 March 2013
|
|
// in favor of draggable containment array
|
|
if (focus > tbounds.last) {
|
|
// past right end of timeline(s): stop leftward drag
|
|
me.dragScopeState = {state:"over-right"};
|
|
} else if (focus < tbounds.first) {
|
|
// over left end of timeline(s): stop rightward drag
|
|
me.dragScopeState = {state:"over-left"};
|
|
} else {
|
|
me.dragScopeState = {state:"okay"};
|
|
}
|
|
*/
|
|
|
|
if (MED.scopeChanges == 1) {
|
|
// first scope change after initial load
|
|
me.initiateNavigation();
|
|
}
|
|
|
|
MED.scopeChanges++;
|
|
});
|
|
|
|
|
|
|
|
// listen for focus date change
|
|
// mainly if date is zipped-to rather than dragged
|
|
$.subscribe(container_name + ".mediator.focusDateChange", function () {
|
|
me.displayFocusDate();
|
|
});
|
|
|
|
|
|
// CREATE TIMELINES MENU
|
|
$.subscribe(container_name + ".mediator.timelineDataLoaded", function (arg) {
|
|
|
|
if (MED.singleTimelineID) {
|
|
me.setupSingleTimeline();
|
|
} else {
|
|
|
|
me.buildTimelineMenu(MED.timelineCollection);
|
|
|
|
if (timeglider.mode == "presentation") {
|
|
me.setupPresentation();
|
|
}
|
|
}
|
|
|
|
me.buildSettingsMenu();
|
|
|
|
me.setupFilter();
|
|
|
|
$(".timeglider-loading").fadeOut(500);
|
|
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.activeTimelinesChange", function () {
|
|
|
|
$(me._views.TIMELINE_MENU_UL + " li").each(function () {
|
|
|
|
var id = $(this).data("timeline_id");
|
|
if (_.indexOf(MED.activeTimelines, id) != -1) {
|
|
$(this).addClass("activeTimeline");
|
|
} else {
|
|
$(this).removeClass("activeTimeline");
|
|
}
|
|
}); // end each
|
|
});
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.filterChange", function () {
|
|
// refresh is done inside MED -- no need to refresh here
|
|
});
|
|
/* END PUB-SUB SUBSCRIBERS */
|
|
|
|
|
|
|
|
$.subscribe(container_name + ".mediator.resize", function () {
|
|
me.resize();
|
|
});
|
|
|
|
|
|
//// GESTURES ////
|
|
/* !!TODO Still a FAIL in iPad ----
|
|
PRIVATE/SCOPED IN CLOSURE, THESE ARE UN-TESTABLE
|
|
*/
|
|
function gestureChange (e) {
|
|
e.preventDefault ();
|
|
if (MED.gesturing === false) {
|
|
MED.gesturing = true;
|
|
MED.gestureStartZoom = MED.getZoomLevel();
|
|
}
|
|
var target = e.target;
|
|
// constant spatial converter value
|
|
//$("#output").append("<br>start zoom:" + MED.gestureStartZoom);
|
|
|
|
// This basically works, but it's funky still....
|
|
var g = Math.ceil(MED.gestureStartZoom / (e.scale / 2));
|
|
|
|
//$("#output").append("<br>new gest zoom:" + g);
|
|
|
|
MED.setZoomLevel(g);
|
|
}
|
|
|
|
|
|
function gestureEnd (e) {
|
|
MED.gesturing = false;
|
|
}
|
|
|
|
if ($.support.touch) {
|
|
// alert("widget:" + WIDGET_ID);
|
|
$("#" + WIDGET_ID).addTouch();
|
|
|
|
var tgcompnt = document.getElementById(WIDGET_ID);
|
|
|
|
tgcompnt.addEventListener("gesturestart", function (e) {
|
|
e.preventDefault();
|
|
$("#output").append("<br>gesture zoom:" + MED.getZoomLevel());
|
|
}, false);
|
|
|
|
tgcompnt.addEventListener("gestureend", function (e) {
|
|
e.preventDefault();
|
|
$("#output").append("<br>gesture end:" + MED.getZoomLevel());
|
|
}, false);
|
|
|
|
|
|
tgcompnt.addEventListener("gesturechange", function (e) {
|
|
e.preventDefault();
|
|
|
|
gestureChange(e);
|
|
//var gLeft = e.touches.item(0).pageX;
|
|
//var gRight = e.touches.item(1).pageX;
|
|
|
|
// var gLeft = "l", gRight = "r";
|
|
// $("#output").append("[" + gLeft + ":" + gRight + "]");
|
|
|
|
}, false);
|
|
|
|
} // end if ($.support.touch)
|
|
|
|
}
|
|
|
|
|
|
|
|
tg.TG_TimelinePlayer.prototype = {
|
|
|
|
|
|
resize: function() {
|
|
|
|
var new_height = $(PL).height();
|
|
$(CONTAINER).height(new_height);
|
|
|
|
// measure stuff
|
|
this.dimensions = this.getWidgetDimensions();
|
|
|
|
|
|
// use measurements to resize various things
|
|
this.resizeElements();
|
|
MED.refresh();
|
|
|
|
},
|
|
|
|
|
|
getWidgetDimensions : function () {
|
|
|
|
var c = $(CONTAINER),
|
|
w = c.width(),
|
|
wc = Math.floor(w / 2) + 1,
|
|
h = c.height(),
|
|
hc = Math.floor(h/2),
|
|
t_height = this.tick_height,
|
|
lft = c.position().left,
|
|
offset = c.offset(),
|
|
f_height = (options.show_footer == true) ? $(this._views.FOOTER).outerHeight() : 0,
|
|
t_top = h - f_height - t_height,
|
|
// objects to return
|
|
ticks_ht = h-(f_height+t_height),
|
|
ticks_rule_ht =
|
|
h_height = $(this._views.HEADER).outerHeight() || 0,
|
|
|
|
// available space for timelines
|
|
tlspace = h - (f_height + h_height + t_height),
|
|
|
|
container = {"width":w, "height":h, "centerx":wc, "centery":hc, "left": lft, "offset": offset},
|
|
ticks = {"height":ticks_ht},
|
|
tick = {"top":t_top, "height":t_height},
|
|
header = {"height":h_height},
|
|
footer = {"height":f_height};
|
|
|
|
var ret = {container:container, ticks:ticks, tick:tick, header:header, footer:footer, timeline_space:tlspace};
|
|
|
|
MED.setDimensions(ret);
|
|
|
|
return ret;
|
|
|
|
},
|
|
|
|
|
|
|
|
initiateNavigation: function() {
|
|
var me = this;
|
|
|
|
$(".tg-single-timeline-header .tg-timeline-start").fadeIn();
|
|
$(CONTAINER).delegate(".tg-single-timeline-header h2", CLICKORTOUCH, function() {
|
|
var tid = $(this).data("timeline_id");
|
|
me.timelineModal(tid);
|
|
MED.focusTimeline(tid);
|
|
|
|
});
|
|
},
|
|
|
|
|
|
displayZoomLevel : function() {
|
|
|
|
var me=this,
|
|
zl = MED.getZoomLevel();
|
|
|
|
if (zl > 0) {
|
|
if (options.display_zoom_level == true) {
|
|
$(me._views.ZOOM_DISPLAY).text(zl);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
displayFocusDate: _.throttle(function () {
|
|
// this is expensive for real-time dragging...
|
|
// without throttle, leads to crashing in Firefox
|
|
var fd = MED.getFocusDate();
|
|
var str = fd.format("d MMM yyyy", true);
|
|
|
|
$(this._views.FOCUS_DATE).find("span").text(str);
|
|
|
|
|
|
|
|
}, 300),
|
|
|
|
|
|
|
|
displayOutOfFrameEvents: _.throttle(function() {
|
|
|
|
var leftOfFrame = MED.getPastEvents(true, true);
|
|
var rightOfFrame = MED.getFutureEvents(true, true);
|
|
|
|
}, 900),
|
|
|
|
|
|
|
|
|
|
/**
|
|
* setPanButton
|
|
* @param $sel {jquery dom selector} the button to be assigned
|
|
* @parm vel {Number} positive for moving to the right, negative for moving left
|
|
*
|
|
*
|
|
*/
|
|
setPanButton : function ($sel, vel) {
|
|
var me = this,
|
|
_int = 33; // 33/1000 second interval
|
|
$($sel).bind("mousedown", function () {
|
|
|
|
me.intervalMachine("pan", {type:"set", fn: me.pan, args:[vel], intvl:_int}); })
|
|
.bind("mouseup", function () {
|
|
me.intervalMachine("pan", {type:"clear", fn: me.pan, callback: "resetTicksHandle"}); })
|
|
.bind("mouseout", function () {
|
|
me.intervalMachine("pan", {type:"clear", fn: me.pan, callback: "resetTicksHandle"}); });
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* intervalMachine
|
|
* param name {String} JS interval ref. name
|
|
* @param info {Object}
|
|
* type: clear | set
|
|
* fn: function to call on interval
|
|
* callback: function to invoke upon clearing
|
|
* eg: {type:"clear", fn: me.pan, callback: "resetTicksHandle"}
|
|
*
|
|
*
|
|
* PLUGIN CANDIDATE!
|
|
|
|
*/
|
|
intervalMachine : function (name, info) {
|
|
var me=this;
|
|
if (info.type === "clear") {
|
|
clearInterval(intervals[name]);
|
|
|
|
if (info.callback) {
|
|
me[info.callback]();
|
|
}
|
|
|
|
} else {
|
|
// run it
|
|
intervals[name] = setInterval(function () {
|
|
info.fn.apply(me, info.args);
|
|
}, info.intvl);
|
|
}
|
|
},
|
|
|
|
|
|
invSliderVal : function(v) {
|
|
return Math.abs(v - 101);
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* pan
|
|
* @param dir {Number}
|
|
* simply moves the ticks one way or another
|
|
* To work properly, it needs a resetTicksHandle() callback;
|
|
* Using this with intervalMachine()
|
|
*/
|
|
pan: function (dir) {
|
|
|
|
var d = dir || 20,
|
|
$t = $(TICKS),
|
|
newPos = $t.position().left + d;
|
|
|
|
$t.css({left:newPos});
|
|
|
|
MED.setTicksOffset(newPos);
|
|
|
|
},
|
|
|
|
|
|
registerTitles: function () {
|
|
|
|
var toff, w, tw, sw, pos, titx,
|
|
$elem, $env, env, $tb, $ti, relPos, tbWidth,
|
|
mo = $(CONTAINER).offset().left,
|
|
trackTB = true;
|
|
|
|
|
|
var tks = $(this._views.TICKS).position().left;
|
|
$(".tg-lane").css("left", (-1 * tks));
|
|
|
|
|
|
$(CONTAINER + " .timeglider-event-spanning").each(
|
|
function() {
|
|
// !TODO needs optimizing of DOM "touching"
|
|
var $spev = $(this);
|
|
toff = $spev.offset().left - mo;
|
|
|
|
$elem = $spev.find(".timeglider-event-title");
|
|
$laneTitle = $spev.find(".timeglider-event-spanner span");
|
|
|
|
tw = $elem.outerWidth() || $laneTitle.outerWidth();
|
|
sw = $spev.find(".timeglider-event-spanner").outerWidth();
|
|
|
|
// if the span is wider than the title element
|
|
if (sw > tw && tw) {
|
|
// if the offset is to the left of the frame
|
|
if (toff < 0) {
|
|
var dif = sw-tw;
|
|
if (Math.abs(toff) < dif) {
|
|
$elem.css({marginLeft:(-1 * toff) + 5});
|
|
$laneTitle.css({marginLeft:(-1 * toff) + 5});
|
|
} else {
|
|
// keep it aligned right if the right side is poking in
|
|
$elem.css({marginLeft:(sw - tw) - 5});
|
|
$laneTitle.css({marginLeft:(sw - tw) - 5});
|
|
}
|
|
// otherwise just keep it aligned on the left side of the span
|
|
} else {
|
|
$elem.css({marginLeft:5});
|
|
}
|
|
}
|
|
// is offscreen == false: $(this).removeClass('timeglider-event-offscreen')
|
|
}
|
|
);
|
|
|
|
// IE 7,8 not able to find the .titleBar element below
|
|
// while this .each is happening. Performance in .find()?
|
|
// This hack just turns off the titleBar tracking... :(
|
|
|
|
/*
|
|
if ($.browser.msie && parseInt($.browser.version) <9) {
|
|
trackTB = false;
|
|
}
|
|
*/
|
|
|
|
// if (trackTB === true) {
|
|
$(CONTAINER + " .tg-timeline-envelope").each(
|
|
function () {
|
|
// !TODO needs optimizing of DOM "touching"
|
|
$env = $(this);
|
|
env = $env.offset().left - mo;
|
|
$tb = $env.find(".titleBar");
|
|
|
|
// `pos` is a pre-cached $tb.position().left;
|
|
// rather than calculating position here, it's
|
|
// grabbing a cached value stored in element data()
|
|
pos = $tb.data("lef");
|
|
|
|
relPos = -1 * (pos + env);
|
|
|
|
$ti = $tb.find(".timeline-title");
|
|
// if it's pushed left of the window
|
|
|
|
|
|
if ( (relPos > 0) ) {
|
|
var dif = $tb.width()-$ti.width();
|
|
if (relPos < dif) {
|
|
$ti.css({marginLeft:relPos + 5});
|
|
} else {
|
|
$ti.css({marginLeft:dif - 5});
|
|
}
|
|
} else {
|
|
$ti.css({marginLeft:5});
|
|
}
|
|
|
|
}
|
|
);
|
|
|
|
}, // end register titles
|
|
|
|
|
|
registerDragging : function () {
|
|
/*
|
|
startSec --> the seconds-value of the
|
|
initial focus date on landing @ zoom level
|
|
*/
|
|
//!TODO: See if we can throttle this to be only
|
|
// once every 100ms....
|
|
var startSec = MED.startSec,
|
|
tickPos = $(TICKS).position().left,
|
|
secPerPx = MED.getZoomInfo().spp;
|
|
|
|
var newSec = startSec - (tickPos * secPerPx);
|
|
|
|
var newD = new TG_Date(newSec);
|
|
|
|
MED.setFocusDate(newD);
|
|
|
|
// remove this???
|
|
this.displayFocusDate();
|
|
|
|
this.displayOutOfFrameEvents();
|
|
|
|
},
|
|
|
|
|
|
|
|
getTimelinesTagsArray: function() {
|
|
|
|
var me=this, tl, tags = [], tags_obj;
|
|
_.each(MED.timelineCollection.models, function(tl) {
|
|
tags_obj = tl.get("tags");
|
|
_.each(tags_obj, function(val,key) {
|
|
tags.push(key);
|
|
});
|
|
});
|
|
|
|
return tags;
|
|
},
|
|
|
|
|
|
|
|
/* FILTER BOX SETUP */
|
|
setupFilter : function () {
|
|
|
|
var me = this,
|
|
$bt = $(me._views.FILTER_BT),
|
|
$filter = $.tmpl(me._templates.filter_modal,{}).appendTo(me._views.CONTAINER),
|
|
use_title = false,
|
|
use_desc = false,
|
|
fbox = me._views.FILTER_BOX;
|
|
|
|
var clearFilterFront = function() {
|
|
MED.setFilters({origin:"title_andor_desc", title:'', tags:'', description:''});
|
|
$(fbox + " .timeglider-filter-search").val('');
|
|
$(fbox + " .timeglider-filter-tags").val('');
|
|
$("#filter-tags").val("").trigger("change");
|
|
}
|
|
|
|
var clearFilters = function() {
|
|
MED.clearFilters({"legend":false, "custom":false});
|
|
}
|
|
|
|
// get tags array from active timelines
|
|
var activeTags = me.getTimelinesTagsArray();
|
|
|
|
if (activeTags.length > 0) {
|
|
|
|
if ($.fn.select2) {
|
|
$("#filter-tags").select2({
|
|
tags:activeTags,
|
|
placeholder:"Click to select",
|
|
allowClear:true
|
|
});
|
|
}
|
|
|
|
} else {
|
|
// no tags -- hide tags element
|
|
$(".filter-tags").hide();
|
|
}
|
|
|
|
|
|
$filter.position({
|
|
my: "right+32 bottom-16",
|
|
at: "right top",
|
|
of: $(me._views.FILTER_BT)
|
|
}).css("z-index", me.ztop++).hide();
|
|
|
|
|
|
$(CONTAINER)
|
|
.delegate(".timeglider-filter-box .tg-close-button", "click", function () {
|
|
clearFilterFront();
|
|
$filter.fadeOut();
|
|
})
|
|
|
|
|
|
$(me._views.FILTER_BT).bind("click", function() {
|
|
|
|
$filter.fadeIn();
|
|
|
|
var $bt = $(this);
|
|
|
|
// If it's never been opened, apply actions to the buttons, etc
|
|
if (me.filterBoxActivated == false) {
|
|
|
|
me.filterBoxActivated = true;
|
|
|
|
var $filter_apply = $(fbox + " .timeglider-filter-apply"),
|
|
$filter_clear = $(fbox + " .timeglider-filter-clear"),
|
|
incl = "", tags = "", excl = "", title_txt = "", desc_txt = "";
|
|
|
|
var $filter_input = $(fbox + " .timeglider-filter-search");
|
|
|
|
$filter_input.on("keydown", function(e) {
|
|
switch(e.which) {
|
|
case 0: case 9: case 13:
|
|
$filter_apply.trigger("click");
|
|
break;
|
|
}
|
|
});
|
|
|
|
|
|
// set up listeners
|
|
$filter_apply.bind("click", function () {
|
|
|
|
clearFilters();
|
|
|
|
tags = $("#filter-tags").val();
|
|
|
|
incl = $(fbox + " .timeglider-filter-search").val();
|
|
excl = ""; // $(fbox + " .timeglider-filter-exclude").val();
|
|
|
|
use_title = $(fbox + " input#filter_t").is(":checked");
|
|
use_desc = $(fbox + " input#filter_d").is(":checked");
|
|
|
|
if ((use_title && incl) || (use_desc && incl) || tags) {
|
|
title_txt = use_title ? incl: "";
|
|
desc_txt = use_desc ? incl: "";
|
|
|
|
if(use_title && use_desc && incl) {
|
|
// EITHER title OR description match
|
|
MED.setFilters({origin:"clude", include:title_txt, exclude:"", tags:tags});
|
|
} else {
|
|
// just title, or just description match
|
|
MED.setFilters({origin:"title_andor_desc", title:title_txt, description:desc_txt, tags:tags});
|
|
}
|
|
|
|
} else {
|
|
// clear
|
|
clearFilters();
|
|
clearFilterFront();
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
$filter_clear.bind("click", function () {
|
|
clearFilters();
|
|
clearFilterFront();
|
|
});
|
|
|
|
} // end if filterBoxActivated
|
|
|
|
}); // end FILTER_BT click
|
|
|
|
|
|
|
|
|
|
|
|
}, // end setupFilter
|
|
|
|
|
|
|
|
|
|
buildTimelineMenu : function () {
|
|
|
|
var me=this;
|
|
var $menu;
|
|
var $bts = $(me._views.FOOTER).find(".tg-footer-buttons");
|
|
var $menu_bt = {};
|
|
|
|
if (! $(me._views.TIMELINE_LIST_BT)[0]) {
|
|
$menu_bt = $("<div class='timeglider-footer-button timeglider-list-bt'></div>").appendTo($bts);
|
|
} else {
|
|
$menu_bt = $(me._views.TIMELINE_LIST_BT);
|
|
}
|
|
|
|
if ($(me._views.TIMELINE_MENU)[0]) {
|
|
$(me._views.TIMELINE_MENU).remove()
|
|
}
|
|
|
|
var $menu = $.tmpl(me._templates.timeline_list_modal,{}).appendTo(me._views.CONTAINER);
|
|
// each timeline's <li> item in menu
|
|
var menuItem = Backbone.View.extend({
|
|
|
|
initialize: function (){
|
|
this.model.bind('change', this.render, this);
|
|
},
|
|
|
|
tagName: "li",
|
|
template: "${title}",
|
|
|
|
events: {
|
|
"click": "toggleTimeline"
|
|
},
|
|
|
|
toggleTimeline : function() {
|
|
MED.toggleTimeline(this.model.get("id"));
|
|
},
|
|
|
|
render: function() {
|
|
var tid = this.model.get("id"), activeClass = "";
|
|
if ( MED.isActive(tid)) activeClass = "activeTimeline";
|
|
|
|
$(this.el).html($.tmpl(this.template, this.model.attributes)).data("timeline_id", tid).addClass(activeClass);
|
|
return this;
|
|
}
|
|
|
|
});
|
|
|
|
|
|
$(me._views.TIMELINE_MENU_UL).html("");
|
|
|
|
_.each(MED.timelineCollection.models, function(model){
|
|
$(me._views.TIMELINE_MENU_UL).append(new menuItem({model:model}).render().el);
|
|
});
|
|
|
|
|
|
$menu.position({
|
|
my: "right+50 bottom-12",
|
|
at: "left top",
|
|
of: $menu_bt
|
|
}).hide();
|
|
|
|
$menu.find(".tg-close-button").bind(CLICKORTOUCH, function () {
|
|
$menu.fadeOut();
|
|
});
|
|
|
|
|
|
$menu_bt.bind(CLICKORTOUCH, function() {
|
|
$menu.fadeIn();
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
getTimezonePulldown: function(id, sel){
|
|
|
|
var html = "<select name='timezone' id='" + id + "'>",
|
|
seld = false, selstr = "selected";
|
|
|
|
$.map(TG_Date.timezones, function(tz){
|
|
|
|
if (sel == tz.offset && seld == false) {
|
|
selstr = "selected";
|
|
seld = true;
|
|
|
|
} else {
|
|
selstr = "";
|
|
}
|
|
|
|
html += "<option value='" + tz.offset + "' " + selstr + ">" + tz.name + "</option>";
|
|
|
|
});
|
|
|
|
html += "</select>";
|
|
return html;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
buildSettingsMenu: function () {
|
|
|
|
var me = this;
|
|
|
|
var $s = $.tmpl(me._templates.settings_modal,{}).appendTo(me._views.CONTAINER);
|
|
|
|
var tz_menu = this.getTimezonePulldown("timeglider-settings-timezone", MED.timeOffset.string);
|
|
|
|
$s.find(".timeglider-settings-timezone")
|
|
.append('<span class="settings-label">timezone:</span> ' + tz_menu + '<br><a class="btn" id="timeglider-settings-save">save</a>');
|
|
|
|
|
|
$s.position({
|
|
my: "right+32 bottom-16",
|
|
at: "right top",
|
|
of: $(me._views.SETTINGS_BT)
|
|
}).hide();
|
|
|
|
|
|
$(CONTAINER)
|
|
.delegate(".timeglider-settings-modal .tg-close-button", "click", function () {
|
|
$s.fadeOut();
|
|
});
|
|
|
|
$(me._views.SETTINGS_BT).bind(CLICKORTOUCH, function() {
|
|
$s.fadeIn();
|
|
});
|
|
|
|
$s.find("#timeglider-settings-save").bind(CLICKORTOUCH, function() {
|
|
// get timezone
|
|
|
|
var tz_off = $(CONTAINER + " #timeglider-settings-timezone").val();
|
|
MED.setTimeoffset(tz_off);
|
|
|
|
$(".timeglider-settings-modal").fadeOut();
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
setupSingleTimeline: function() {
|
|
|
|
var me = this, dims = {},
|
|
tid = MED.singleTimelineID,
|
|
timeline = MED.timelineCollection.get(tid);
|
|
|
|
|
|
|
|
if (MED.options.display_single_timeline_info != false) {
|
|
|
|
var title = "<h2 data-timeline_id='" + tid + "'>" + timeline.get("title") + "</h2>";
|
|
|
|
inf = (timeline.get("description")) ? "<li id='info' class='timeline-info-bt' data-timeline_id='" + tid + "'>info</li>":"",
|
|
|
|
leg = (timeline.get("hasLegend")) ? "<li id='legend' class='tg-legend-bt' data-timeline_id='" + tid + "'>legend</li>":"",
|
|
|
|
tools = ""; // "<a id='tools' class='tools-bt noselect'>tools</a>",
|
|
|
|
tmpl = "<div class='tg-widget-header tg-single-timeline-header'><ul><li class='tg-timeline-start' data-timeline_id='" + tid + "'>start</li></ul></div>";
|
|
|
|
$st = $(tmpl).appendTo(CONTAINER);
|
|
|
|
me.singleTitleHeight = $st.outerHeight();
|
|
|
|
} else {
|
|
|
|
me.singleTitleHeight = 0;
|
|
}
|
|
|
|
|
|
me.dimensions = dims = me.getWidgetDimensions();
|
|
|
|
|
|
if (timeline.get("hasImageLane")) {
|
|
me.buildImageLane();
|
|
}
|
|
|
|
|
|
// adjusts the zoom slider away from the timeline bar at top
|
|
if (options.inverted == true) {
|
|
|
|
//
|
|
$(me._views.SLIDER_CONTAINER).css({"bottom":"16px","top":"auto"});
|
|
$(me._views.FOOTER).css({"top":dims.header.height + "px"});
|
|
timeline.set({"bottom":(dims.timeline_space - 70), "inverted":true});
|
|
options.tick_top = dims.container.height - (dims.timeline_space + me.tick_height);
|
|
|
|
} else {
|
|
$(me._views.SLIDER_CONTAINER).css("top", me.singleTitleHeight + 4);
|
|
|
|
}
|
|
|
|
|
|
if (options.initial_timeline_modal != false) {
|
|
me.timelineModal(tid);
|
|
}
|
|
|
|
|
|
if (timeline.get("hasLegend")) {
|
|
setTimeout(function() {
|
|
me.legendModal(tid);
|
|
}, 500);
|
|
}
|
|
|
|
|
|
},
|
|
|
|
//////// MODALS
|
|
presentationModal : function () {
|
|
|
|
var me = this;
|
|
|
|
var pmodal = $(CONTAINER).find("#presInfoModal");
|
|
|
|
if (!pmodal[0]) {
|
|
|
|
if (MED.presentation.description) {
|
|
|
|
var ch = me.dimensions.container.height,
|
|
modal = new this.presInfoModal({model:MED.presentation});
|
|
var $header = $(CONTAINER).find(".tg-pres-header");
|
|
|
|
$(modal.render().el)
|
|
.appendTo($(".timeglider-container"))
|
|
.position({
|
|
my: "left top",
|
|
at: "left+12 bottom+12",
|
|
of: $header,
|
|
collision: "flip fit"
|
|
})
|
|
.css({"z-index":me.ztop++, "max-height":ch-64});
|
|
|
|
if ($.jScrollPane) {
|
|
$(".jscroll").jScrollPane();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
//////// MODALS
|
|
datePickerOpen: false,
|
|
datepickerModal : function () {
|
|
|
|
var me = this;
|
|
var $modal = {};
|
|
|
|
if (!me.datepickerOpen) {
|
|
var me = this,
|
|
ch = me.dimensions.container.height,
|
|
modal = new this.datepicker({model:{}}),
|
|
|
|
$modal = $(modal.render().el)
|
|
.appendTo($(".timeglider-container"))
|
|
.position({
|
|
my: "center bottom-39",
|
|
at: "center bottom",
|
|
of: $(".timeglider-container"),
|
|
collision: "fit fit"
|
|
})
|
|
.css({"z-index":me.ztop++, "max-height":ch-64});
|
|
|
|
me.datepickerOpen = true;
|
|
|
|
} else {
|
|
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startPresentation: function() {
|
|
|
|
var me = this,
|
|
pres = MED.presentation;
|
|
|
|
MED.gotoDateZoom(pres.focus_date.dateStr, pres.initial_zoom);
|
|
|
|
},
|
|
|
|
|
|
setupPresentation: function() {
|
|
|
|
var me = this,
|
|
pres = MED.presentation;
|
|
|
|
var title = "<h2 id='pres_title' class='no-select' data-timeline_id='primary'>" + pres.title + "</h2>",
|
|
|
|
inf = (pres.description) ? "<li id='info' class='pres-info-bt'>info</li>":"",
|
|
|
|
leg = (pres.legend) ? "<li id='legend' data-timeline_id='pres' class='tg-legend-bt'>legend</li>":"",
|
|
|
|
tools = "", // "<a id='tools' class='tools-bt noselect'>tools</a>",
|
|
|
|
tmpl = "<div class='tg-widget-header tg-pres-header'>" + title + "<ul>" + inf + leg + "<li class='tg-pres-start'>start</li></ul>" + tools + "</div>",
|
|
|
|
$st = $(tmpl).appendTo(CONTAINER);
|
|
|
|
// end vars
|
|
me.getWidgetDimensions();
|
|
|
|
|
|
me.singleTitleHeight = $st.outerHeight();
|
|
|
|
|
|
if (pres.image_lane_height) {
|
|
me.buildImageLane();
|
|
} // end if has imagelane
|
|
|
|
// adjusts the zoom slider away from the timeline bar at top
|
|
$(me._views.SLIDER_CONTAINER).css("top", me.singleTitleHeight + 4);
|
|
|
|
me.startPresentation();
|
|
|
|
if (pres.open_modal && pres.description) {
|
|
me.presentationModal();
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
buildImageLane: function() {
|
|
|
|
var me = this;
|
|
|
|
var $existing = $(CONTAINER).find(".tg-image-lane-pull");
|
|
|
|
if ($existing.length == 0) {
|
|
|
|
var $imageLane = $("<div class='tg-image-lane-pull'><div title='This is the image lane!' class='tg-image-lane-bg'></div></div>").appendTo(CONTAINER);
|
|
|
|
$imageLane.draggable({
|
|
|
|
axis:"y",
|
|
containment: "parent",
|
|
drag: function () {
|
|
var $pull = $(this);
|
|
var ypos = $pull.position().top;
|
|
|
|
if (ypos > 400) {
|
|
$pull.css("top", 400);
|
|
return false;
|
|
} else if (ypos < 5) {
|
|
$pull.css("top", 5);
|
|
return false;
|
|
}
|
|
},
|
|
stop:function() {
|
|
MED.setImageLaneHeight($(this).position().top - me.singleTitleHeight, true, false);
|
|
}
|
|
});
|
|
|
|
me.setImageLaneHandle();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* setImageLaneHandle
|
|
* gets image_lane_height from MED and sets image lane
|
|
* UI remotely (not from dragging, but from timeline/pres props)
|
|
*/
|
|
setImageLaneHandle: function () {
|
|
|
|
var me = this;
|
|
var newHt = parseInt(MED.image_lane_height, 10) + parseInt(me.singleTitleHeight, 10);
|
|
|
|
$(".tg-image-lane-pull").css("top", newHt + "px");
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
Zoom slider is inverted value-wise from the normal jQuery UI slider
|
|
so we need to feed in and take out inverse values with invSliderVal()
|
|
*/
|
|
|
|
buildSlider : function () {
|
|
|
|
var me = this,
|
|
iz = MED.getZoomLevel();
|
|
|
|
if (options.min_zoom == options.max_zoom) {
|
|
// With a single zoom level, hide the zoom controller
|
|
$(this._views.SLIDER_CONTAINER).css("display", "none");
|
|
|
|
} else {
|
|
|
|
if (options.display_zoom_level == true) {
|
|
var $zl = $("<div>").appendTo(this._views.SLIDER_CONTAINER).addClass("timeglider-zoomlevel-display");
|
|
$zl.html(' ');
|
|
|
|
}
|
|
|
|
var me = this,
|
|
init_zoom = me.invSliderVal(iz),
|
|
hZoom = MED.max_zoom,
|
|
lZoom = MED.min_zoom,
|
|
sHeight = (1 + hZoom - lZoom) * 3;
|
|
|
|
$(this._views.SLIDER)
|
|
.css({"height":sHeight})
|
|
.slider({
|
|
steps: 100,
|
|
handle: $('.knob'),
|
|
animate:300,
|
|
orientation: 'vertical',
|
|
|
|
// "min" here is really the _highest_ zoom value @ upside down
|
|
min:me.invSliderVal(hZoom),
|
|
|
|
// "max" actually takes (i nverse value of) low zoom level
|
|
max:me.invSliderVal(lZoom),
|
|
|
|
value:init_zoom,
|
|
|
|
start: function (e, ui) {
|
|
// show zoom level legend
|
|
me.sliderActive = true;
|
|
},
|
|
|
|
stop: function (e, ui) {
|
|
// hide zoom level legend
|
|
me.sliderActive = false;
|
|
},
|
|
|
|
change: function(e,ui){
|
|
// i.e. on-release handler
|
|
// possibly load throttled back events
|
|
},
|
|
|
|
slide: function(e, ui) {
|
|
// sets model zoom level to INVERSE of slider value
|
|
MED.setZoomLevel(me.invSliderVal(ui.value));
|
|
}
|
|
}); // end .slider()
|
|
|
|
|
|
|
|
} // end--if min_zoom == max_zoom
|
|
},
|
|
|
|
|
|
setupScroller: function() {
|
|
|
|
if (options.event_overflow == "scroll") {
|
|
|
|
$(".timeglider-slider-container").css("right", "22px");
|
|
|
|
$(CONTAINER + " .tg-scroller-handle").draggable({
|
|
containment: "parent",
|
|
drag: function() {
|
|
// move content vertically
|
|
}
|
|
});
|
|
|
|
}
|
|
},
|
|
|
|
adjustScroller: function(low, high) {
|
|
var me = this;
|
|
if (options.event_overflow == "scroll") {
|
|
var $scr = $(CONTAINER + " .tg-scroller");
|
|
var $handle = $(CONTAINER + " .tg-scroller-handle");
|
|
var scr_ht = me.dimensions.scroller_height;
|
|
var evts_ht = high + (Math.abs(low));
|
|
|
|
var overage = evts_ht / scr_ht;
|
|
|
|
if (overage > 1) {
|
|
|
|
} else {
|
|
$scr.hide();
|
|
}
|
|
|
|
}
|
|
},
|
|
|
|
|
|
/*
|
|
* usage: timeline event hovering, modal display
|
|
*
|
|
*/
|
|
getEventDateLine: function(ev) {
|
|
|
|
var startDateF = "<span class='timeglider-dateline-startdate'>" + ev.startdateObj.format('', true, MED.timeOffset) + "</span>",
|
|
|
|
endDateF = "";
|
|
|
|
if (ev.span == true) {
|
|
endDateF = " – <span class='timeglider-dateline-enddate'>" + ev.enddateObj.format('', true, MED.timeOffset) + "</span>";
|
|
}
|
|
|
|
var ret = startDateF + endDateF;
|
|
|
|
return ret;
|
|
|
|
},
|
|
|
|
|
|
|
|
eventHover : function ($ev, ev) {
|
|
|
|
var follow = true;
|
|
|
|
if (typeof MED.options.eventHover == "function") {
|
|
|
|
follow = MED.options.eventHover($ev, ev, this);
|
|
|
|
}
|
|
|
|
if (follow) {
|
|
|
|
var me = this,
|
|
$hov = $(".timeglider-event-hover-info"),
|
|
title = "",
|
|
date_line = "";
|
|
|
|
$ev.append("<div class='tg-event-hoverline'></div>").addClass("tg-event-hovered");
|
|
|
|
if (ev.date_display == "no") {
|
|
date_line = "";
|
|
} else {
|
|
date_line = me.getEventDateLine(ev);
|
|
}
|
|
|
|
if ($ev.hasClass("tg-event-collapsed") || $ev.hasClass("tg-event-overflow")) {
|
|
title = "<div>" + ev.title + "</div>";
|
|
} else {
|
|
title = "";
|
|
}
|
|
|
|
if (title || date_line) {
|
|
$hov.position({
|
|
my: "left+1 top+4",
|
|
at: "left bottom",
|
|
of: $ev,
|
|
collision: "flip flip"}
|
|
)
|
|
.html(title + date_line)
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
eventUnHover : function ($ev) {
|
|
var $ev = $ev || "";
|
|
|
|
if (typeof MED.options.eventUnHover == "function") {
|
|
MED.options.eventUnHover($ev, this);
|
|
} else {
|
|
$(".timeglider-event-hover-info").css("left", "-1000px");
|
|
$(".timeglider-timeline-event").removeClass("tg-event-hovered");
|
|
if ($ev) {
|
|
$ev.find(".tg-event-hoverline").remove();
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
|
|
clearTicks : function () {
|
|
this.leftside = 0;
|
|
this.tickNum = 0;
|
|
|
|
$(TICKS)
|
|
.css("left", 0);
|
|
// .html("<div class='timeglider-handle'></div>");
|
|
|
|
// remove everything but HANDLE, which
|
|
// needs to stay so that gesturing (pinching to zoom)
|
|
// doesn't lose its target
|
|
$(CONTAINER + " .tg-timeline-envelope").remove();
|
|
$(CONTAINER + " .timeglider-tick").remove();
|
|
|
|
|
|
},
|
|
|
|
|
|
/*
|
|
The initial drawing of a full set of ticks, starting in the
|
|
middle with a single, date-focused div with type:"init", after which
|
|
a left-right alternating loop fills out the width of the current frame
|
|
*/
|
|
castTicks: function (orig) {
|
|
|
|
if (MED.viewMode == "timeline") {
|
|
|
|
this.clearTicks();
|
|
|
|
var zLevel = MED.getZoomLevel(),
|
|
fDate = MED.getFocusDate(),
|
|
zInfo = MED.getZoomInfo(),
|
|
tickWidth = zInfo.width,
|
|
twTotal = 0,
|
|
ctr = this.dimensions.container.centerx,
|
|
// determine how many are necessary to fill (overfill) container
|
|
nTicks = Math.ceil(this.dimensions.container.width / tickWidth) + 4,
|
|
leftright = 'l';
|
|
|
|
if (typeof zInfo.width == "number") {
|
|
|
|
MED.setTicksReady(false);
|
|
|
|
// INITIAL TICK added in center according to focus date provided
|
|
|
|
this.addTick({"type":"init", "focus_date":fDate});
|
|
|
|
// ALTERNATING L & R ticks
|
|
for (var i=1; i<=nTicks; i +=1) {
|
|
this.addTick({"type":leftright});
|
|
// switch l and r for alternating layout action
|
|
leftright = (leftright == "l") ? "r" : "l";
|
|
}
|
|
|
|
MED.setTicksReady(true);
|
|
|
|
this.displayFocusDate();
|
|
|
|
}
|
|
|
|
} else {
|
|
// i.e. viewMode == list
|
|
this.clearTicks();
|
|
this.freshTimelines();
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
getTickTop: function () {
|
|
|
|
var tttype = typeof MED.options.tick_top;
|
|
|
|
if (tttype == "number") {
|
|
// default number, zero for ticks at top
|
|
return MED.options.tick_top;
|
|
} else if (tttype == "function") {
|
|
// could be a custom setter function
|
|
return MED.options.tick_top(me.dimensions);
|
|
} else {
|
|
// at the bottom
|
|
return parseInt(this.dimensions.tick.top);
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* @param info {object} --object-->
|
|
* type: init|l|r
|
|
* focusDate: date object for init type
|
|
*/
|
|
addTick: function (info) {
|
|
|
|
var me = this, mDays = 0, dist = 0, pos = 0,
|
|
tperu = 0, serial = 0, shiftLeft = 0, ctr = 0,
|
|
tid = "", tickHtml = "", sub_label = "", label = {},
|
|
$tickDiv = {}, tInfo = {}, pack = {}, mInfo = {},
|
|
sub_labels = "", sub_labels_arr = [], oeClass = '',
|
|
|
|
tickUnit = MED.getZoomInfo().unit,
|
|
tickWidth = MED.getZoomInfo().width,
|
|
focusDate = MED.getFocusDate(),
|
|
tick_top = me.getTickTop(),
|
|
serial = MED.addToTicksArray({type:info.type, unit:tickUnit}, focusDate);
|
|
// end vars comma list
|
|
|
|
// adjust tick-width for months (mo)
|
|
if (tickUnit == "mo") {
|
|
// starts with default tickWidth set for 28 days: How many px, days to add?
|
|
mInfo = TG_Date.getMonthAdj(serial, tickWidth);
|
|
tickWidth = mInfo.width;
|
|
mDays = mInfo.days;
|
|
|
|
}
|
|
|
|
// tickNum has been reset to zero by refresh/zoom
|
|
this.tickNum ++;
|
|
|
|
if (info.type == "init") {
|
|
|
|
shiftLeft = this.tickOffsetFromDate(MED.getZoomInfo(), MED.getFocusDate(), tickWidth);
|
|
|
|
pos = Math.ceil(this.dimensions.container.centerx + shiftLeft);
|
|
$(TICKS).data("init-left", pos);
|
|
// both and left and right sides are defined
|
|
// here because it is the first tick on screen
|
|
this.leftside = pos;
|
|
this.rightside = (pos + tickWidth);
|
|
|
|
|
|
} else if (info.type == "l") {
|
|
pos = Math.floor(this.leftside - tickWidth);
|
|
this.leftside = pos;
|
|
} else if (info.type == "r") {
|
|
pos = Math.floor(this.rightside);
|
|
this.rightside += tickWidth;
|
|
}
|
|
|
|
// turn this into a function...
|
|
MED.getTickBySerial(serial).width = tickWidth;
|
|
MED.getTickBySerial(serial).left = pos;
|
|
|
|
oeClass = (serial % 2 == 0) ? "tg-even-tick": "tg-odd-tick";
|
|
|
|
tid = this._views.PLACE + "_" + tickUnit + "_" + serial + "-" + this.tickNum;
|
|
|
|
$tickDiv= $("<div class='timeglider-tick " + oeClass + "' id='" + tid + "'><div class='tg-tick-body'><div class='tg-tick-leftline'></div><div class='timeglider-tick-label'></div><div class='tg-tick-label-bottom'></div></div>")
|
|
.appendTo(TICKS);
|
|
|
|
|
|
$tickDiv.css({width:tickWidth, left:pos, top:tick_top, zIndex:55});
|
|
|
|
// GET TICK DIVS FOR unit AND width
|
|
tInfo = this.getTickMarksInfo({unit:tickUnit, width:tickWidth});
|
|
// if there's a value for month-days, us it, or use
|
|
// tperu = (mDays > 0) ? mDays : tInfo.tperu;
|
|
tperu = mDays || tInfo.tperu;
|
|
|
|
dist = tickWidth / tperu;
|
|
|
|
// Add tick-lines or times when divisions are spaced wider than 5
|
|
|
|
if (dist > 8) {
|
|
|
|
// As of Jan 29, 2012, no more Raphael!
|
|
|
|
var c, l, xd, stk = '', sl4hd = 0,
|
|
ht = 10, downset = 20, hr_info = {}, ampm = '',
|
|
lpos = 0;
|
|
|
|
for (l = 0; l < tperu; l++) {
|
|
|
|
sub_label = " ";
|
|
|
|
|
|
if (dist > 16) {
|
|
|
|
if (tickUnit == "da") {
|
|
// hours starting with 0
|
|
sub_label = me.getHourLabelFromHour(l, dist);
|
|
|
|
} else if (tickUnit == "mo") {
|
|
// days starting with 1
|
|
sub_label = l + 1;
|
|
} else if (tickUnit == "ye") {
|
|
if (dist > 30){
|
|
// Jan, Feb, Mar...
|
|
sub_label = " " + TG_Date.monthNamesAbbr[l+1];
|
|
} else {
|
|
// month abbrevs: J, F, M...
|
|
sub_label = " " + TG_Date.monthNamesLet[l+1];
|
|
}
|
|
} else if (tickUnit == "de") {
|
|
if (dist > 54){
|
|
sub_label = (serial *10) + l;
|
|
}
|
|
} else if (tickUnit == "ce") {
|
|
if (dist > 38){
|
|
sub_label = ((serial *10) + l) * 10;
|
|
}
|
|
}
|
|
|
|
|
|
} else {
|
|
// less than 16
|
|
sub_label = "";
|
|
}
|
|
|
|
|
|
sub_labels_arr.push("<div class='timeglider-tick-sub-label " + tickUnit + "' style='left:" + lpos + "px;width:" + dist + "px'>" + sub_label + "</div>");
|
|
|
|
|
|
|
|
|
|
lpos += dist;
|
|
}
|
|
|
|
if (serial < 0) {
|
|
sub_labels_arr.reverse();
|
|
}
|
|
|
|
sub_labels = sub_labels_arr.join("");
|
|
|
|
} else {
|
|
sub_labels = "";
|
|
}// end dist > 5 if there's enough space between tickmarks
|
|
|
|
// add hours gathered in loop above
|
|
if (sub_labels) {
|
|
$tickDiv.append("<div class='tg-tick-sublabel-group' style='width:" + (tickWidth + 10) + "px;'>" + sub_labels + "</div>");
|
|
}
|
|
|
|
pack = {"unit":tickUnit, "width":tickWidth, "serial":serial};
|
|
|
|
label = this.getDateLabelForTick(pack);
|
|
|
|
// In order to gather whether an outlier span is
|
|
// occuring on drag-right (the right side of a span)
|
|
// we need some seconds...
|
|
|
|
pack.seconds = this.getTickSeconds[tickUnit](pack.serial);
|
|
|
|
// DO OTHER STUFF TO THE TICK, MAKE THE LABEL AN ACTIONABLE ELEMENT
|
|
// SHOULD APPEND WHOLE LABEL + TICKLINES HERE
|
|
|
|
$tickDiv.find(".timeglider-tick-label").text(label);
|
|
|
|
var lb_offset = (MED.options.show_footer) ? 46 : 14;
|
|
var ht = this.dimensions.container.height - lb_offset;
|
|
|
|
$tickDiv.find(".tg-tick-label-bottom").text(label).css("top", ht);
|
|
|
|
return pack;
|
|
|
|
/* end addTick */
|
|
},
|
|
|
|
|
|
resizeElements: function () {
|
|
|
|
var me = this,
|
|
|
|
// measurements have just been taken...
|
|
cx = me.dimensions.container.centerx,
|
|
ch = me.dimensions.container.height,
|
|
cw = me.dimensions.container.width,
|
|
fh = me.dimensions.footer.height,
|
|
th = me.dimensions.tick.height,
|
|
$C = $(this._views.CENTERLINE),
|
|
$D = $(this._views.DATE),
|
|
dleft = cx - ($D.width() / 2);
|
|
|
|
if (MED.options.show_centerline === true) {
|
|
$C.css({"height":ch, "left": cx});
|
|
} else {
|
|
$C.css({"display":"none"});
|
|
}
|
|
|
|
var ticks_ht = ch-(fh+th);
|
|
$(this._views.TICKS).css("height", ticks_ht);
|
|
|
|
var scroller_ht = ticks_ht - 4;
|
|
if (options.event_overflow == "scroll") {
|
|
me.dimensions.scroller_height = scroller_ht;
|
|
$(this._views.CONTAINER).find(".tg-scroller").css({
|
|
height:scroller_ht + "px"
|
|
});
|
|
} else {
|
|
me.dimensions.scroller_height = 0;
|
|
$(this._views.CONTAINER).find(".tg-scroller").remove();
|
|
}
|
|
|
|
|
|
if (ch < 500 || cw < 500 ) {
|
|
$(".timeglider-slider").css("display", "none");
|
|
} else {
|
|
$(".timeglider-slider").css("display", "block");
|
|
}
|
|
|
|
$D.css({"left":dleft});
|
|
|
|
},
|
|
|
|
/*
|
|
* @param pack {Object} `unit` and `serial`
|
|
*/
|
|
getTickSeconds: {
|
|
da: function(ser) {
|
|
var s = ser * 86400,
|
|
e = s + 86400;
|
|
return {start:s, end:e};
|
|
},
|
|
mo: function(ser) {
|
|
var s = ser * 2629800,
|
|
e = s + 2629800;
|
|
return {start:s, end:e};
|
|
},
|
|
ye: function(ser) {
|
|
var s = ser * 31540000,
|
|
e = s + 31540000;
|
|
return {start:s, end:e};
|
|
},
|
|
de: function(ser) {
|
|
var s = ser * 315400000,
|
|
e = s + 315400000;
|
|
return {start:s, end:e};
|
|
},
|
|
ce: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
thou: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
tenthou: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
hundredthou: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
mill: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
tenmill: function(ser) {
|
|
var s = ser * 3154000000,
|
|
e = s + 3154000000;
|
|
return {start:s, end:e};
|
|
},
|
|
hundredmill: function(ser) {
|
|
var s = ser * 31540000000,
|
|
e = s + 31540000000;
|
|
return {start:s, end:e};
|
|
},
|
|
bill: function(ser) {
|
|
var s = ser * 315400000000,
|
|
e = s + 315400000000;
|
|
return {start:s, end:e};
|
|
}
|
|
|
|
},
|
|
|
|
|
|
getHourLabelFromHour : function (h24, width) {
|
|
|
|
var ampm = "", htxt = h24, bagels = "", sublabel = "", sl4hd = 0;
|
|
|
|
if (width < 16) {
|
|
// no room for anything; will just be ticks
|
|
return '';
|
|
} else {
|
|
|
|
if (h24 > 12) {
|
|
htxt = h24-12;
|
|
} else if (h24 == 0) {
|
|
htxt = 12;
|
|
}
|
|
|
|
if (width > 30) {
|
|
ampm = (h24 > 11) ? " pm" : " am";
|
|
}
|
|
|
|
if (width > 200) {
|
|
sl4hd = width/4 - 4;
|
|
|
|
return "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":00 " + ampm + "</div>"
|
|
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":15 " + ampm + "</div>"
|
|
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":30 " + ampm + "</div>"
|
|
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":45 " + ampm + "</div>";
|
|
|
|
} else {
|
|
bagels = (width > 60) ? ":00" : "";
|
|
return htxt + bagels + ampm;
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/* provides addTick() info for marks and for adj width for month or year */
|
|
getTickMarksInfo : function (obj) {
|
|
var tperu;
|
|
switch (obj.unit) {
|
|
case "da":
|
|
tperu = 24;
|
|
break;
|
|
case "mo":
|
|
// this is adjusted for different months later
|
|
tperu = 30;
|
|
break;
|
|
case "ye":
|
|
tperu = 12;
|
|
break;
|
|
default: tperu = 10;
|
|
}
|
|
|
|
return {"tperu":tperu};
|
|
},
|
|
|
|
/*
|
|
* getDateLabelForTick
|
|
* determines label for date unit in "ruler"
|
|
* @param obj {object} carries these values:
|
|
{"unit":tickUnit, "width":tickWidth, "serial":serial}
|
|
*
|
|
*/
|
|
getDateLabelForTick : function (obj) {
|
|
|
|
var i, me=this, ser = obj.serial, tw = obj.width;
|
|
|
|
switch(obj.unit) {
|
|
|
|
case "bill":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + " billion";
|
|
} else {
|
|
return (ser) + " b.y. bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "hundredmill":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + "00 million";
|
|
} else {
|
|
return (ser) + "00 m.y. bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "tenmill":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + "0 million";
|
|
} else {
|
|
return (ser) + "0 m.y. bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "mill":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + " million";
|
|
} else {
|
|
return (ser) + " m.y. bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "hundredthou":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + "00,000";
|
|
} else {
|
|
return (ser) + "00,000 bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "tenthou":
|
|
if (ser == 0) {
|
|
return "1";
|
|
} else if (ser > 0) {
|
|
return (ser) + "0,000";
|
|
} else {
|
|
return (ser) + "0,000 bce";
|
|
}
|
|
break;
|
|
|
|
case "thou":
|
|
if (ser == 0) {
|
|
return "1" + "(" + ser + ")";
|
|
} else if (ser > 0) {
|
|
return (ser) + "000";
|
|
} else {
|
|
return (ser) + "000 bce";
|
|
}
|
|
break;
|
|
|
|
case "ce":
|
|
if (ser == 0) {
|
|
return "1" + "(" + ser + ")";
|
|
} else if (ser > 0) {
|
|
return (ser) + "00";
|
|
} else {
|
|
return (ser) + "00 bce";
|
|
}
|
|
break;
|
|
|
|
|
|
case "de":
|
|
if (ser > 120){
|
|
return (ser * 10) + "s";
|
|
} else {
|
|
return (ser * 10);
|
|
}
|
|
break;
|
|
|
|
case "ye":
|
|
return ser;
|
|
break;
|
|
|
|
case "mo":
|
|
|
|
i = TG_Date.getDateFromMonthNum(ser);
|
|
|
|
if (tw < 120) {
|
|
return TG_Date.monthNamesAbbr[i.mo] + " " + i.ye;
|
|
} else {
|
|
return TG_Date.monthNames[i.mo] + ", " + i.ye;
|
|
}
|
|
break;
|
|
|
|
|
|
case "da":
|
|
|
|
// COSTLY: test performance here on dragging
|
|
i = new TG_Date(TG_Date.getDateFromRD(ser));
|
|
|
|
if (tw < 120) {
|
|
return i.ye + "-" + TG_Date.monthNamesAbbr[i.mo] + "-" + i.da ;
|
|
} else {
|
|
return i.ye + "-" + TG_Date.monthNames[i.mo] + "-" + i.da;
|
|
}
|
|
break;
|
|
|
|
default: return obj.unit + ":" + ser + ":" + tw;
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
* tickHangies
|
|
* When dragging the interface, we detect when to add a new
|
|
* tick on left or right side: whether the outer tick has
|
|
* come within a 100px margin of the left or right of the frame
|
|
*
|
|
*/
|
|
tickHangies : function () {
|
|
var tPos = $(TICKS).position().left,
|
|
lHangie = this.leftside + tPos,
|
|
rHangie = this.rightside + tPos - this.dimensions.container.width,
|
|
tickPack, added = false,
|
|
me = this;
|
|
|
|
if (lHangie > -100) {
|
|
tickPack = this.addTick({"type":"l"});
|
|
me.appendTimelines(tickPack, "left");
|
|
} else if (rHangie < 100) {
|
|
tickPack = this.addTick({"type":"r"});
|
|
me.appendTimelines(tickPack, "right");
|
|
}
|
|
},
|
|
|
|
|
|
/* tickUnit, fd */
|
|
tickOffsetFromDate : function (zoominfo, fdate, tickwidth) {
|
|
|
|
// switch unit, calculate width gain or loss.... or just loss!
|
|
var w = tickwidth,
|
|
u = zoominfo.unit, p, prop;
|
|
|
|
switch (u) {
|
|
case "da":
|
|
// @4:30 4/24 30 / 1440
|
|
// .1666 .0201
|
|
prop = ((fdate.ho) / 24) + ((fdate.mi) / 1440);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "mo":
|
|
|
|
var mdn = TG_Date.getMonthDays(fdate.mo, fdate.ye);
|
|
|
|
prop = ((fdate.da -1) / mdn) + (fdate.ho / (24 * mdn)) + (fdate.mi / (1440 * mdn));
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "ye":
|
|
prop = (((fdate.mo - 1) * 30) + fdate.da) / 365;
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "de":
|
|
//
|
|
// 1995
|
|
prop = ((fdate.ye % 10) / 10) + (fdate.mo / 120);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "ce":
|
|
prop = ((fdate.ye % 100) / 100) + (fdate.mo / 1200);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "thou":
|
|
prop = ((fdate.ye % 1000) / 1000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "tenthou":
|
|
|
|
prop = ((fdate.ye % 10000) / 10000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "hundredthou":
|
|
|
|
prop = ((fdate.ye % 100000) / 100000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "mill":
|
|
|
|
prop = ((fdate.ye % 1000000) / 1000000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "tenmill":
|
|
|
|
prop = ((fdate.ye % 10000000) / 10000000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "hundredmill":
|
|
|
|
prop = ((fdate.ye % 100000000) / 100000000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
case "bill":
|
|
|
|
prop = ((fdate.ye % 1000000000) / 1000000000);
|
|
p = w * prop;
|
|
break;
|
|
|
|
default: p=0;
|
|
|
|
}
|
|
|
|
return -1 * p;
|
|
},
|
|
|
|
|
|
resetTicksHandle : function () {
|
|
var me = this;
|
|
$(me._views.HANDLE).offset({"left":$(CONTAINER).offset().left});
|
|
},
|
|
|
|
|
|
throttledResetTicksHandle: _.throttle(function () {
|
|
this.resetTicksHandle();
|
|
}, 500),
|
|
|
|
|
|
easeOutTicks : function() {
|
|
|
|
if (Math.abs(ticksSpeed) > 5) {
|
|
// This works, but isn't great:offset fails to register
|
|
// for new tim as it ends animation...
|
|
|
|
$(TICKS).animate({left: '+=' + (3 * ticksSpeed)}, 1000, "easeOutQuad", function() {
|
|
debug.trace("stopping easing", "note")
|
|
});
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
@param obj with { tick | timeline }
|
|
@return array of event ids
|
|
This is per-timeline...
|
|
*/
|
|
getTimelineEventsByTick : function (obj) {
|
|
|
|
var unit = obj.tick.unit,
|
|
serial = obj.tick.serial,
|
|
hash = MED.eventCollection.getTimelineHash(obj.timeline.timeline_id);
|
|
|
|
if (hash[unit][serial] && hash[unit][serial].length > 0) {
|
|
// looking for an array of events...
|
|
return hash[unit][serial];
|
|
} else {
|
|
return [];
|
|
}
|
|
|
|
},
|
|
|
|
|
|
passesFilters : function (ev, zoomLevel) {
|
|
var ret = true,
|
|
ev_icon = "",
|
|
ei = "", ea = [], e, titl, desc,
|
|
ii = "", ia = [], da = [], i;
|
|
|
|
// MASTER FILTER BY THRESHOLD
|
|
if ((zoomLevel < ev.low_threshold) || (zoomLevel > ev.high_threshold)) {
|
|
return false;
|
|
}
|
|
|
|
if (MED.filters.imp_min && MED.filters.imp_min > 1) {
|
|
if (ev.importance < MED.filters.imp_min) { return false; }
|
|
}
|
|
|
|
if (MED.filters.imp_max && MED.filters.imp_max < 100) {
|
|
if (ev.importance > MED.filters.imp_max) { return false; }
|
|
}
|
|
|
|
|
|
if (MED.filters.custom && typeof MED.filters.custom == "function") {
|
|
ret = MED.filters.custom(ev, zoomLevel);
|
|
|
|
} else {
|
|
|
|
|
|
if (MED.filters.include) {
|
|
// title OR description
|
|
ret = false;
|
|
var incl = MED.filters.include;
|
|
ea = incl.split(",");
|
|
for (e=0; e<ea.length; e++) {
|
|
ei = new RegExp($.trim(ea[e]), "i");
|
|
if (ev.title.match(ei) || ev.description.match(ei)) { ret = true; }
|
|
}
|
|
|
|
} else {
|
|
|
|
// KEYWORDS FOR SHOWING THIS EVENT
|
|
if (MED.filters.title) {
|
|
|
|
titl = MED.filters.title;
|
|
ia = titl.split(",");
|
|
ret = false;
|
|
// cycle through comma separated include keywords
|
|
for (i=0; i<ia.length; i++) {
|
|
ii = new RegExp($.trim(ia[i]), "i");
|
|
if (ev.title.match(ii)) { ret = true; }
|
|
}
|
|
}
|
|
|
|
// KEYWORDS FOR SHOWING THIS EVENT
|
|
if (MED.filters.description) {
|
|
|
|
desr = MED.filters.description;
|
|
da = desr.split(",");
|
|
ret = false;
|
|
// cycle through comma separated include keywords
|
|
for (i=0; i<da.length; i++) {
|
|
ii = new RegExp($.trim(da[i]), "i");
|
|
if (ev.description.match(ii)) { ret = true; }
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (MED.filters.exclude) {
|
|
var excl = MED.filters.exclude;
|
|
ea = excl.split(",");
|
|
for (e=0; e<ea.length; e++) {
|
|
ei = new RegExp($.trim(ea[e]), "i");
|
|
if (ev.title.match(ei) || ev.description.match(ei)) { ret = false; }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TAGS FILTER
|
|
if (MED.filters.tags.length > 0) {
|
|
if (ev.tags) {
|
|
// we'll assume it's not going to match first
|
|
ret = false;
|
|
ev_tags = ev.tags.split(",");
|
|
_.each(ev_tags, function(tag) {
|
|
tag = $.trim(tag);
|
|
if (_.indexOf(MED.filters.tags, tag) !== -1) {
|
|
ret = true;
|
|
}
|
|
});
|
|
} else {
|
|
// event has no tags at all..
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// LEGEND FILTER
|
|
if (MED.filters.legend.length > 0) {
|
|
ev_icon = ev.icon;
|
|
if (_.indexOf(MED.filters.legend, ev_icon) == -1) {
|
|
// if it's not in the legend list
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
} // end if/else for custom filter function
|
|
|
|
/////////////
|
|
|
|
return ret;
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
ADDING EVENTS ON INITIAL SWEEP!
|
|
invoked upon a fresh sweep of entire container, having added a set of ticks
|
|
--- occurs on expand/collapse
|
|
--- ticks are created afresh
|
|
*/
|
|
freshTimelines : function () {
|
|
|
|
var me = this,
|
|
t, i, tl, tlView, tlModel, tu, ts, tick, tE, tl_ht, t_f, t_l,
|
|
active = MED.activeTimelines,
|
|
ticks = MED.ticksArray,
|
|
borg = '',
|
|
$title, $ev, $tl,
|
|
evid, ev,
|
|
stuff = '',
|
|
cx = me.dimensions.container.centerx,
|
|
cw = me.dimensions.container.width,
|
|
foSec = MED.getFocusDate().sec,
|
|
zi = MED.getZoomInfo(),
|
|
spp = zi.spp,
|
|
zl = zi.level,
|
|
tickUnit = zi.unit,
|
|
tArr = [],
|
|
idArr = [],
|
|
// left and right scope
|
|
half = Math.floor(spp * (cw/2)),
|
|
lsec = foSec - half,
|
|
rsec = foSec + half,
|
|
tz_offset = 0, tbwidth = 0,
|
|
spanin,
|
|
legend_label = "",
|
|
spanins = [],
|
|
expCol, tl_top=0,
|
|
cht = me.dimensions.container.height,
|
|
ceiling = 0,
|
|
tl_min_bottom = MED.options.minimum_timeline_bottom,
|
|
ticks_ht = me.dimensions.ticks.height;
|
|
|
|
|
|
/////////////////////////////////////////
|
|
|
|
/*
|
|
var testDate = MED.getFocusDate();
|
|
|
|
var tdFocus = Math.floor(testDate.sec);
|
|
|
|
var tickSec = me.getTickSeconds['da'](testDate.rd);
|
|
*/
|
|
|
|
//////////////////////////////////////////
|
|
$.publish(container_name + ".viewer.rendering");
|
|
|
|
for (var a=0; a<active.length; a++) {
|
|
|
|
idArr = [];
|
|
|
|
// FOR EACH _ACTIVE_ TIMELINE...
|
|
tlModel = MED.timelineCollection.get(active[a]);
|
|
|
|
|
|
|
|
tl = tlModel.attributes;
|
|
|
|
tl.visibleEvents = [];
|
|
|
|
if (MED.viewMode == "timeline") {
|
|
|
|
|
|
|
|
expCol = tl.display;
|
|
|
|
// TODO establish the 120 below in some kind of constant!
|
|
// meanwhile: tl_top is the starting height of a loaded timeline
|
|
tl_bottom = (tl.bottom) ? stripPx(tl.bottom) : tl_min_bottom;
|
|
if (tl_bottom < tl_min_bottom) tl_bottom = tl_min_bottom;
|
|
|
|
tl_top = ticks_ht - tl_bottom;
|
|
|
|
tl_min_bottom = MED.options.minimum_timeline_bottom;
|
|
|
|
tlView = new tg.TG_TimelineView({model:tlModel});
|
|
|
|
tz_offset = MED.timeOffset.seconds / spp;
|
|
|
|
$tl = $(tlView.render().el).appendTo(TICKS);
|
|
|
|
var laneTops = {};
|
|
|
|
|
|
if (tlModel.get('hasLanes') && tlModel.get('useLanes')) {
|
|
// clear existing lanes
|
|
$(".tg-lane").remove();
|
|
var $lane, lane_top = 32, ltop;
|
|
|
|
_.each(tlModel.get('lanes'), function(lane) {
|
|
|
|
ltop = lane.top || (lane_top +=30);
|
|
$lane = $("<div class='tg-lane'>" + lane.title + "<div class='arrow-right'></div></div>");
|
|
|
|
$lane.appendTo($tl)
|
|
.draggable({
|
|
axis:"y",
|
|
stop:function(event, ui) {
|
|
lane.top = Math.abs($(event.target).position().top);
|
|
MED.refresh()
|
|
}
|
|
})
|
|
.css({"top": -1 * ltop, "border-left":"4px solid " + lane.color})
|
|
.find(".arrow-right").css({"border-left":"10px solid " + lane.color})
|
|
|
|
lane.top = laneTops[lane.id] = Math.abs(ltop);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
$title = $tl.find(".titleBar");
|
|
// this is the individual (named) timeline, not the entire interface
|
|
|
|
|
|
// if a single timeline, set images to the bottom
|
|
var tbh = $title.outerHeight();
|
|
|
|
me.room = tl_top; // (cht - (Math.abs(tl_top) + tbh)) - (me.dimensions.footer.height + me.dimensions.tick.height);
|
|
|
|
|
|
$tl.draggable({
|
|
axis:"y",
|
|
handle:".titleBar",
|
|
|
|
stop: function () {
|
|
|
|
|
|
var posi = $(this).position();
|
|
|
|
// chrome doesn't allow access the new bottom
|
|
var new_bottom = (ticks_ht - stripPx($(this).css("top"))) -1;
|
|
|
|
if (new_bottom < tl_min_bottom) {
|
|
$(this).css("bottom", tl_min_bottom);
|
|
new_bottom = tl_min_bottom;
|
|
}
|
|
|
|
var tid = $(this).attr("id");
|
|
|
|
// if we've dragged the timeline up or down
|
|
// reset its .top value and refresh, mainly
|
|
// to reset ceiling (+/visible) properties
|
|
var tl = MED.timelineCollection.get(tid);
|
|
tl.set({bottom:new_bottom});
|
|
|
|
// if a single timeline, set images to the bottom
|
|
var tbh = $title.outerHeight();
|
|
|
|
me.room = me.dimensions.ticks.height - new_bottom;
|
|
|
|
MED.refresh();
|
|
}
|
|
})
|
|
.css({"bottom":tl_bottom, "left": tz_offset});
|
|
|
|
|
|
|
|
if (typeof tl.bounds != "undefined") {
|
|
|
|
t_f = cx + ((tl.bounds.first - foSec) / spp);
|
|
t_l = cx + ((tl.bounds.last - foSec) / spp);
|
|
} else {
|
|
// if no events, we have to make this up
|
|
t_f = cx;
|
|
t_l = cx + 300;
|
|
}
|
|
|
|
tbwidth = Math.floor(t_l - t_f);
|
|
|
|
var tmax = 1000000;
|
|
var farl = -1 * (tmax - 2000);
|
|
|
|
// browsers have a maximum width for divs before
|
|
// they konk out... if we get to a high point, we
|
|
// can truncate the div, but have to make sure to
|
|
// equally adjust the left position if the right
|
|
// end of the div is needing to be placed in-screen
|
|
// whew.
|
|
if (tbwidth > tmax) {
|
|
var dif = tbwidth - tmax;
|
|
tbwidth = tmax;
|
|
if (t_f < farl) {
|
|
t_f = t_f + dif;
|
|
}
|
|
}
|
|
|
|
$title.css({"top":tl_ht, "left":t_f, "width":tbwidth}).data({"lef":t_f, "wid":tbwidth});
|
|
|
|
/// for initial sweep display, setup fresh borg for organizing events
|
|
if (expCol == "expanded") { tl.borg = borg = new timeglider.TG_Org(); }
|
|
|
|
|
|
var tick;
|
|
//cycle through ticks for hashed events
|
|
for (var tx=0; tx<ticks.length; tx++) {
|
|
tick = ticks[tx];
|
|
tArr = this.getTimelineEventsByTick({tick:tick, timeline:tl});
|
|
idArr = _.union(idArr, tArr);
|
|
}
|
|
|
|
tl.visibleEvents = idArr;
|
|
|
|
// detect if there are boundless spans (bridging, no start/end points)
|
|
|
|
_.each(tl.spans, function (spanin) {
|
|
|
|
if (_.indexOf(idArr, spanin.id) === -1) {
|
|
|
|
if ((spanin.start < lsec && spanin.end > rsec)
|
|
|| (spanin.end < rsec && spanin.end > lsec)) {
|
|
|
|
// adds to beginning to prioritize
|
|
idArr.unshift(spanin.id);
|
|
tl.visibleEvents.push(spanin.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
// clean out dupes with _.uniq
|
|
stuff = this.compileTickEventsAsHtml(tl, _.uniq(idArr), 0, "sweep", tickUnit);
|
|
|
|
|
|
// future planning for scrollable overflow
|
|
if (options.event_overflow == "scroll") {
|
|
|
|
ceiling = 10000;
|
|
|
|
} else {
|
|
|
|
//!TODO: does ANY timeline have an image lane??
|
|
if (tl.inverted) {
|
|
ceiling = tl_bottom - 16;
|
|
|
|
} else {
|
|
ceiling = (tl.hasImageLane || tg.mode == "authoring") ? (tl_top - MED.image_lane_height) - me.singleTitleHeight : tl_top - me.singleTitleHeight ;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var onIZoom = (tl.initial_zoom == MED.getZoomLevel());
|
|
|
|
|
|
if (expCol == "expanded") {
|
|
stuff = borg.getHTML({tickScope:"sweep", ceiling:ceiling, onIZoom:onIZoom, inverted:tl.inverted, laneTops:laneTops});
|
|
tl.borg = borg.getBorg();
|
|
}
|
|
|
|
if (stuff != "undefined") {
|
|
$tl.append(stuff.html);
|
|
me.adjustScroller(stuff.high, stuff.low);
|
|
}
|
|
|
|
setTimeout( function() {
|
|
me.registerEventImages($tl);
|
|
}, 100);
|
|
|
|
|
|
|
|
////////////
|
|
/////////////////////
|
|
// MOBILE LIST MODE
|
|
/////////////////////
|
|
////////////
|
|
} else if (MED.viewMode == "list") {
|
|
|
|
// CREATE "hideTimelineControls"
|
|
|
|
/*
|
|
$(".tg-footer-center").hide();
|
|
$(".timeglider-slider-container").hide();
|
|
$(".tg-image-lane-pull").hide();
|
|
$(".timeglider-truck").hide();
|
|
$(".timeglider-centerline").hide();
|
|
*/
|
|
|
|
// WIPE OUT ENTIRE ELEMENT EACH TIME
|
|
$("#tg-list-wrapper").remove();
|
|
|
|
// AND RECREATE
|
|
var $tg_list_container = $("<div id='tg-list-wrapper'><div id='tg-list' class='tg-list-events'><ul id='list-events-ul' class='list-ul'></ul></div></div>").prependTo(CONTAINER);
|
|
|
|
|
|
// remember, this is inside a or loop!
|
|
var hash = MED.eventCollection.getTimelineHash(tlModel.get("id"));
|
|
var evts = [];
|
|
|
|
_.each(hash.all, function(evid) {
|
|
ev = MED.eventCollection.get(evid).attributes;
|
|
// with list, ignore zoom level
|
|
|
|
if (me.passesFilters(ev, 0) === true) {
|
|
evts.push(ev);
|
|
}
|
|
});
|
|
|
|
// change this to TG_OrgList
|
|
var borg = new timeglider.TG_OrgList(evts, MED);
|
|
|
|
|
|
stuff = borg.getHTML();
|
|
// EVERYTHING ADDED HERE
|
|
|
|
|
|
if (stuff != "undefined") { $(".tg-list-events ul").append(stuff.html); }
|
|
|
|
|
|
var testing_touch = true;
|
|
|
|
|
|
if (CLICKORTOUCH == "touchstart" || testing_touch) {
|
|
tg.listIScroll = new iScroll('tg-list-wrapper');
|
|
} else {
|
|
// apply jscrollpane to list ?
|
|
if ($.fn.jScrollPane) {
|
|
$(".tg-list-events").jScrollPane();
|
|
}
|
|
|
|
tg.listIScroll = {animating:false};
|
|
}
|
|
|
|
setTimeout(function() {
|
|
$(".tg-legend .tg-legend-close").trigger(CLICKORTOUCH);
|
|
}, 300);
|
|
|
|
// use click even with mobile view
|
|
$(".tg-list-li").bind("click", function() {
|
|
if (tg.listIScroll.distance > 20 || tg.listIScroll.moved) return false;
|
|
var $ev = $(this);
|
|
var evid = $ev.attr("id");
|
|
me.eventModal(evid, $ev);
|
|
});
|
|
|
|
} // end list/timeline if
|
|
|
|
|
|
|
|
|
|
}// end for each timeline
|
|
|
|
|
|
// initial title shift since it's not on-drag
|
|
me.registerTitles();
|
|
|
|
|
|
setTimeout(function () {
|
|
me.applyFilterActions();
|
|
MED.setInitialScope();
|
|
}, 300);
|
|
|
|
|
|
$.publish(container_name + ".viewer.rendered");
|
|
|
|
}, // ends freshTimelines()
|
|
|
|
|
|
|
|
/*
|
|
* appendTimelines
|
|
* @param tick {Object} contains serial, time-unit, and more info
|
|
*/
|
|
appendTimelines : function (tick, side) {
|
|
|
|
var active = MED.activeTimelines,
|
|
idArr = [],
|
|
tModel, $tl, tl, f,
|
|
stuff = "", diff = 0,
|
|
ceiling = 0,
|
|
me = this;
|
|
|
|
$.publish(container_name + ".viewer.rendering");
|
|
|
|
for (var a=0; a<active.length; a++) {
|
|
|
|
tModel = MED.timelineCollection.get(active[a]);
|
|
tl = tModel.attributes;
|
|
|
|
// get the events from timeline model hash
|
|
idArr = this.getTimelineEventsByTick({tick:tick, timeline:tl});
|
|
|
|
tl.visibleEvents = _.union(tl.visibleEvents, idArr);
|
|
|
|
tl_top = (tl.top) ? stripPx(tl.top) : (me.initTimelineVOffset);
|
|
|
|
// we need to see if the right end of a long span
|
|
// is present in the newly added tick
|
|
if (side == "left") {
|
|
|
|
_.each(tl.spans, function (spanin) {
|
|
|
|
//var diff = tick.seconds.start - spanin.end;
|
|
if (spanin.end < tick.seconds.end && spanin.end > tick.seconds.start) {
|
|
|
|
|
|
//not already in array
|
|
if (_.indexOf(tl.visibleEvents, spanin.id) === -1) {
|
|
// add to beginning to prioritize
|
|
idArr.unshift(spanin.id);
|
|
tl.visibleEvents.push(spanin.id);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
var laneTops = {};
|
|
|
|
if (tModel.get('hasLanes') && tModel.get('useLanes')) {
|
|
|
|
_.each(tModel.get('lanes'), function(lane) {
|
|
laneTops[lane.id] = Math.abs(lane.top);
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// this either puts it into the timeline's borg object
|
|
// or, if compressed, creates HTML for compressed version.
|
|
// stuff here would be null if expanded...
|
|
stuff = this.compileTickEventsAsHtml(tl, idArr, tick.serial, "append", tick.unit);
|
|
|
|
// TODO: make 56 below part of layout constants collection
|
|
if (options.event_overflow == "scroll") {
|
|
ceiling = 10000;
|
|
} else {
|
|
ceiling = (tl.hasImageLane) ? (tl_top - MED.image_lane_height) - me.singleTitleHeight : tl_top;
|
|
}
|
|
|
|
|
|
var onIZoom = (tl.initial_zoom == MED.getZoomLevel());
|
|
|
|
// borg it if it's expanded.
|
|
if (tl.display == "expanded"){
|
|
// tl.top is the ceiling
|
|
stuff = tl.borg.getHTML({tickScope:tick.serial, ceiling:ceiling, onIZoom:onIZoom, inverted:tl.inverted, laneTops:laneTops});
|
|
}
|
|
|
|
var $vu = $(CONTAINER + " .tg-timeline-envelope#" + tl.id);
|
|
|
|
$vu.append(stuff.html);
|
|
|
|
me.adjustScroller(stuff.low, stuff.high);
|
|
|
|
this.registerEventImages($tl);
|
|
|
|
} // end for() in active timelines
|
|
|
|
// this needs to be delayed because the append usually
|
|
// happens while dragging, which already brings the
|
|
// browser to the processor limits; make timeout time
|
|
// below larger if things are crashing : )
|
|
setTimeout(function () { me.applyFilterActions(); }, 500);
|
|
|
|
$.publish(container_name + ".viewer.rendered");
|
|
|
|
}, // end appendTimelines()
|
|
|
|
|
|
|
|
|
|
|
|
// events array, MED, tl, borg,
|
|
// "sweep" vs tick.serial (or fresh/append)
|
|
/*
|
|
*
|
|
* @param btype {String} "sweep" || "append"
|
|
*
|
|
*
|
|
*/
|
|
compileTickEventsAsHtml : function (tl, idArr, tick_serial, btype, tickUnit) {
|
|
|
|
|
|
var me=this,
|
|
posx = 0,
|
|
cx = this.dimensions.container.centerx,
|
|
expCol = tl.display,
|
|
ht = 0,
|
|
stuff = "",
|
|
foSec = MED.startSec,
|
|
zi = MED.getZoomInfo(),
|
|
spp = zi.spp,
|
|
zl = zi.level,
|
|
buffer = 16,
|
|
img_ht = 0,
|
|
img_wi = 0,
|
|
borg = tl.borg || "",
|
|
ev = {},
|
|
font_ht = 0,
|
|
shape = {},
|
|
colTop = 0,
|
|
impq,
|
|
block_arg = "sweep"; // default for initial load
|
|
|
|
|
|
if (borg) tl.borg.clearFresh();
|
|
|
|
|
|
var isBig = function(tu) {
|
|
if (tu == "da" || tu == "mo" || tu == "ye" || tu == "de" || tu == "ce" || tu == "thou"){
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
if (btype == "append") {
|
|
block_arg = tick_serial;
|
|
}
|
|
|
|
for (var i=0; i<idArr.length; i++) {
|
|
|
|
// BBONE
|
|
ev = MED.eventCollection.get(idArr[i]).attributes;
|
|
|
|
if (this.passesFilters(ev, zl) === true) {
|
|
|
|
// the larger units (>=thou) have have an error
|
|
// in their placement from long calculations;
|
|
// we can compensate for them here...
|
|
var adjust = (isBig(tickUnit)) ? .99795 : 1;
|
|
var ev_sds = ev.startdateObj.sec * adjust;
|
|
|
|
posx = cx + ((ev_sds - foSec) / spp);
|
|
|
|
|
|
if (expCol == "expanded") {
|
|
|
|
impq = (tl.size_importance === true || tl.size_importance === 1) ? tg.scaleToImportance(ev.importance, zl) : 1;
|
|
|
|
ev.width = (ev.titleWidth * impq) + buffer;
|
|
ev.fontsize = MED.base_font_size * impq;
|
|
ev.left = posx;
|
|
ev.spanwidth = 0;
|
|
|
|
if (ev.span == true) {
|
|
ev.spanwidth = ((ev.enddateObj.sec - ev.startdateObj.sec) / spp);
|
|
if (ev.spanwidth > ev.width) { ev.width = ev.spanwidth + buffer; }
|
|
}
|
|
|
|
img_ht = 0;
|
|
|
|
font_ht = Math.ceil(ev.fontsize);
|
|
|
|
ev.height = ev.fixed_height || (font_ht + 4);
|
|
ev.top = ht - ev.height;
|
|
ev.bottom = 0;
|
|
|
|
if (ev.image && ev.image.display_class == "inline") {
|
|
|
|
var img_scale = (ev.image.scale || 100) / 100;
|
|
img_ht = (img_scale * ev.image.height) + 2;
|
|
img_wi = (img_scale * ev.image.width) + 2;
|
|
|
|
// !TODO
|
|
// THIS NEEDS TO BE REVERSABLE WITH POLARITY
|
|
// WORKS ONLY WITH BOTTOM-UP CURRENTLY
|
|
|
|
ev.shape = {
|
|
"img_ht":img_ht,
|
|
"img_wi":img_wi,
|
|
"title": "shape",
|
|
"top": (ev.top - (img_ht + 8)),
|
|
"bottom": ev.bottom,
|
|
"left": ev.left,
|
|
"right":ev.left + (img_wi + 8)
|
|
};
|
|
|
|
|
|
} else {
|
|
ev.shape = "";
|
|
}
|
|
|
|
|
|
// block_arg is either "sweep" for existing ticks
|
|
// or the serial number of the tick being added by dragging
|
|
borg.addBlock(ev, block_arg);
|
|
|
|
// end expanded state
|
|
|
|
} else if (expCol == "collapsed") {
|
|
if (tl.inverted) {
|
|
colTop = 4;
|
|
} else {
|
|
colTop = ht - 20;
|
|
}
|
|
|
|
colIcon = (ev.icon) ? tg.icon_folder + ev.icon: tg.icon_folder + "shapes/circle_white.png";
|
|
|
|
stuff += "<div id='" + ev.id +
|
|
"' class='timeglider-timeline-event tg-event-collapsed' style='top:" +
|
|
colTop + "px;left:" + posx + "px'><img src='" + colIcon + "'></div>";
|
|
}
|
|
} // end if it passes filters
|
|
|
|
}
|
|
|
|
if (expCol == "collapsed") {
|
|
return {html:stuff};
|
|
} else {
|
|
// if expanded, "stuff" is
|
|
// being built into the borg
|
|
return "";
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/*
|
|
* registerEventImages
|
|
* Events can have classes applied to their images; these routines
|
|
* take care of doing non-css-driven positioning after the layout
|
|
* has finished placing events in the tick sphere.
|
|
*
|
|
*
|
|
*/
|
|
registerEventImages : function ($timeline) {
|
|
var me = this,
|
|
laneHt = MED.image_lane_height,
|
|
padding = 4,
|
|
laneMax = 400,
|
|
stht = this.singleTitleHeight;
|
|
|
|
if (laneHt > laneMax) { laneHt = laneMax; }
|
|
|
|
|
|
$(CONTAINER + " .timeglider-event-image-lane").each(
|
|
|
|
function () {
|
|
|
|
var $div = $(this),
|
|
imgHt = laneHt - (padding/2),
|
|
$img = $(this).find("img"),
|
|
imax = parseInt($div.data("max_height"), 10) || laneMax;
|
|
|
|
if (imax < imgHt) {
|
|
imgHt = imax;
|
|
}
|
|
|
|
if (imgHt > 10) {
|
|
$div.css({"display":"block"})
|
|
.position({
|
|
my: "center top+" + (stht + padding),
|
|
at: "center top",
|
|
of: $(CONTAINER)
|
|
})
|
|
.css({left:0});
|
|
|
|
$img.css("height", imgHt - (padding));
|
|
|
|
} else {
|
|
$div.css({"display":"none"});
|
|
}
|
|
}
|
|
);
|
|
|
|
|
|
},
|
|
|
|
applyFilterActions: function() {
|
|
|
|
var fa = MED.filterActions,
|
|
collection = MED.eventCollection.models,
|
|
ev_id;
|
|
|
|
if (!_.isEmpty(fa)) {
|
|
|
|
// For performance reasons, having just
|
|
// one filter function is probably smart : )
|
|
_.each(fa, function (f) {
|
|
// filter:actionFilter, fn:actionFunction
|
|
|
|
_.each(collection, function (ev) {
|
|
if (f.filter(ev)) {
|
|
ev_id = ev.get("id");
|
|
// it's passed the filter, so run it through
|
|
// the action function
|
|
f.fn($(".timeglider-timeline-event#" + ev_id));
|
|
}
|
|
});
|
|
|
|
})
|
|
}
|
|
|
|
},
|
|
|
|
|
|
expandCollapseTimeline : function (id) {
|
|
var tl = MED.timelineCollection.get(id).attributes;
|
|
if (tl.display == "expanded") {
|
|
tl.display = "collapsed";
|
|
} else {
|
|
tl.display = "expanded";
|
|
}
|
|
MED.refresh();
|
|
},
|
|
|
|
|
|
invertTimeline : function (id) {
|
|
var tl = MED.timelineCollection.get(id).attributes;
|
|
if (tl.inverted == false) {
|
|
tl.inverted = true;
|
|
} else {
|
|
tl.inverted = false;
|
|
}
|
|
MED.refresh();
|
|
},
|
|
|
|
|
|
|
|
//////// MODALS
|
|
timelineModal : function (id) {
|
|
|
|
$(".tg-timeline-modal").remove();
|
|
|
|
var me = this,
|
|
tl = MED.timelineCollection.get(id),
|
|
$modal;
|
|
|
|
if (tl.get("description")) {
|
|
|
|
var ch = me.dimensions.container.height,
|
|
modal = new this.timelineInfoModal({model:tl});
|
|
|
|
var hh = $(".tg-widget-header").outerHeight() + 32;
|
|
|
|
|
|
var tgcht = $(".timeglider-container").position();
|
|
|
|
|
|
$modal = $(modal.render().el)
|
|
.appendTo("body")
|
|
.position({
|
|
my: "left+16 top+39",
|
|
at: "left top",
|
|
of: $(".timeglider-container"),
|
|
collision: "none none"
|
|
})
|
|
.css({"z-index":me.ztop++, "max-height":ch-64});
|
|
|
|
if (MED.singleTimelineID) {
|
|
$modal.find("h4").hide();
|
|
}
|
|
|
|
|
|
|
|
if ($.fn.jScrollPane) {
|
|
$(".jscroll").jScrollPane();
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/*
|
|
* Generates a horizontal menu of all links
|
|
* in the event's link_json array
|
|
*/
|
|
createEventLinksMenu : function (linkage) {
|
|
if (!linkage) return "";
|
|
|
|
var html = '', l = 0, lUrl = "", lLab="";
|
|
|
|
if (typeof(linkage) == "string") {
|
|
// single url string for link: use "link"
|
|
html = "<li><a href='" + linkage + "' target='_blank'>link</a></li>"
|
|
} else if (typeof(linkage) == "object"){
|
|
// array of links with labels and urls
|
|
for (l=0; l<linkage.length; l++) {
|
|
lUrl = linkage[l].url;
|
|
lLab = linkage[l].label;
|
|
html += "<li><a href='" + lUrl + "' target='_blank'>" + lLab + "</a></li>"
|
|
}
|
|
}
|
|
return html;
|
|
},
|
|
|
|
|
|
|
|
eventModal : function (eid, $event) {
|
|
|
|
// remove if same event already has modal opened
|
|
$.publish(container_name + ".viewer.eventModal");
|
|
|
|
// this removes a duplicate modal
|
|
$(CONTAINER + " #" + eid + "_modal").remove();
|
|
|
|
var me = this,
|
|
map_view = false,
|
|
video_view=false,
|
|
map = "", map_options = {}, $modal, llar=[], mapZoom = 0,
|
|
|
|
ev = MED.eventCollection.get(eid).attributes,
|
|
|
|
// modal type: first check event, then timeline-wide option
|
|
modal_type = ev.modal_type || options.event_modal.type;
|
|
|
|
var ev_img = (ev.image && ev.image.src) ? "<img src='" + ev.image.src + "'>" : "",
|
|
|
|
links = this.createEventLinksMenu(ev.link),
|
|
|
|
templ_obj = {
|
|
id:ev.id,
|
|
title:ev.title,
|
|
description:ev.description,
|
|
link:ev.link,
|
|
dateline: me.getEventDateLine(ev),
|
|
links:links,
|
|
image:ev_img
|
|
}
|
|
|
|
if (ev.video) {
|
|
templ_obj.video = ev.video;
|
|
modal_type = "full";
|
|
video_view = true;
|
|
templ_obj.video = ev.video;
|
|
} else if (ev.map && ev.map.latlong) {
|
|
map_view = true;
|
|
modal_type = "full";
|
|
|
|
// if the embed size is small
|
|
} else if ((ev.description.length > 1200) || (me.dimensions.container.width < 500)) {
|
|
modal_type = "full";
|
|
}
|
|
// return false;
|
|
|
|
switch (modal_type) {
|
|
|
|
case "full":
|
|
|
|
$modal = $.tmpl(me._templates.event_modal_full,templ_obj);
|
|
// full modal with scrim, etc
|
|
var pad = 32;
|
|
|
|
$modal
|
|
.appendTo(CONTAINER)
|
|
.position({
|
|
my: "left top",
|
|
at: "left top",
|
|
of: (CONTAINER),
|
|
collision: "none none"
|
|
});
|
|
|
|
var ch = me.dimensions.container.height;
|
|
var cw = me.dimensions.container.width;
|
|
var $panel = $modal.find(".tg-full_modal_panel");
|
|
var pw = cw - 64;
|
|
var ph = ch - 64;
|
|
var iw = 0;
|
|
|
|
$panel.css({
|
|
"width":pw,
|
|
"height":ph,
|
|
"top":"32px",
|
|
"left":"32px"
|
|
});
|
|
|
|
var $pp = $(".tg-full_modal-body");
|
|
var pph = ph-120;
|
|
|
|
|
|
if (map_view == true) {
|
|
|
|
// REPLACE WITH OPEN STREET MAPS / LEAFLET
|
|
//////////////////////////////////////////
|
|
$map = $("<div id='map_modal_map' class='tg-modal_map'></div>").prependTo($pp);
|
|
|
|
mapZoom = ev.map.zoom || 12;
|
|
var llarr = String(ev.map.latlong).split(",");
|
|
|
|
var map_ll = new google.maps.LatLng(parseFloat(llarr[0]), parseFloat(llarr[1]));
|
|
map_options = {
|
|
zoom:mapZoom,
|
|
center: map_ll,
|
|
mapTypeId: google.maps.MapTypeId.ROADMAP
|
|
}
|
|
map = new google.maps.Map($("#map_modal_map")[0], map_options);
|
|
|
|
// if there are markers provided in the map:
|
|
|
|
if (ev.map.markers) {
|
|
|
|
for (var i=0; i<ev.map.markers.length; i++) {
|
|
var marker = ev.map.markers[i];
|
|
var image = new google.maps.MarkerImage(marker.image,
|
|
new google.maps.Size(24, 32),
|
|
new google.maps.Point(0,0),
|
|
new google.maps.Point(0, 32)); // "plant" origin is lower left
|
|
|
|
var loc = marker.latlong.split(",");
|
|
|
|
var llobj = new google.maps.LatLng(loc[0], loc[1]);
|
|
|
|
var marker = new google.maps.Marker({
|
|
position: llobj,
|
|
map: map,
|
|
icon: marker.icon,
|
|
title: marker.title,
|
|
zIndex:marker.zIndex
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
} else if (video_view == true) {
|
|
|
|
$vid = $("<div class='tg-modal-video'><iframe frameborder='0' src='" + ev.video + "'></iframe></div>").appendTo(".tg-full_modal-vidimg");
|
|
|
|
if (ph > 450) {
|
|
$vid.find("iframe").css("height", $vid.width() * .66);
|
|
|
|
} else {
|
|
var mini_h = ph - 80;
|
|
var mini_w = mini_h * 1.5;
|
|
$vid.find("iframe").css({"float":"left", "height":mini_h, "width":mini_w});
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev.image) {
|
|
var $img = $(".tg-full_modal-body img");
|
|
var img_max_ht = ph-110;
|
|
|
|
if (ev.image.height > img_max_ht) {
|
|
|
|
$img.css("height", img_max_ht);
|
|
|
|
} else if (ev.image.width < pw/3) {
|
|
// small image
|
|
iw = ev.image.width;
|
|
$img.css("width", iw);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if ($pp.height() > pph-16) {
|
|
$pp.css({"height":pph-16, "overflow-y":"scroll"});
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "link-iframe":
|
|
// show the link (i.e. Wikipedia, etc) in an iframe
|
|
|
|
$modal = $.tmpl(me._templates.event_modal_iframe,templ_obj);
|
|
$modal
|
|
.appendTo(TICKS)
|
|
.css("z-index", me.ztop++)
|
|
.position({
|
|
my: "center top+32",
|
|
at: "center top",
|
|
of: $(CONTAINER),
|
|
collision: "flip fit"
|
|
})
|
|
.hover(function () { $(this).css("z-index", me.ztop++); });
|
|
|
|
|
|
break;
|
|
|
|
case "custom":
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
templ_obj.extra_class = (templ_obj.image) ? "has-image":"no-image";
|
|
|
|
// .appendTo(CONTAINER);
|
|
// works, but modal does not follow drag
|
|
|
|
$modal = $.tmpl(me._templates.event_modal_small,templ_obj).appendTo(me._views.TICKS);
|
|
|
|
var pad = 8,
|
|
arrow_class = "",
|
|
tb_class = "",
|
|
lr_class = "",
|
|
ev_left = "",
|
|
ev_top = $event.position().top,
|
|
ev_off = $event.offset();
|
|
|
|
var co_ht = me.dimensions.container.height;
|
|
var co_wi = me.dimensions.container.width;
|
|
var co_off = me.dimensions.container.offset;
|
|
|
|
var modal_ht = $modal.outerHeight();
|
|
var modal_wi = $modal.outerWidth();
|
|
// compensate for height of timeline envelope
|
|
var env_top = $event.closest(".tg-timeline-envelope").position().top;
|
|
var space_above = (env_top + ev_top) - 28;
|
|
|
|
var space_below = Math.abs(ev_top);
|
|
var pos_my = "", pos_at = "";
|
|
|
|
var ev_rel = ev_off.left - co_off.left;
|
|
var farthest = me.dimensions.container.width - (modal_wi + pad);
|
|
|
|
if (ev_rel < pad) {
|
|
// shift to the left
|
|
ev_left = "+" + String(pad + (ev_rel * -1));
|
|
} else if (ev_rel > farthest) {
|
|
// it's too far off to the right
|
|
// find amount that $modal width
|
|
// plus event offset_left exceeds
|
|
// container width!
|
|
var leftward = co_wi - ev_rel;
|
|
var exc = modal_wi - leftward;
|
|
ev_left = "-" + (pad * 4 + exc);
|
|
}
|
|
|
|
if (space_above > modal_ht + 12) {
|
|
// position above
|
|
pos_my = "left"+ev_left+" bottom-12";
|
|
pos_at = "left top";
|
|
} else if (space_below > modal_ht){
|
|
// position modal below the event
|
|
pos_my = "left"+ev_left+" top+8";
|
|
pos_at = "left bottom";
|
|
} else {
|
|
pos_my = "center"+ev_left+" center";
|
|
pos_at = "center center";
|
|
}
|
|
|
|
$modal
|
|
.css({"z-index": me.ztop++})
|
|
.position({
|
|
my: pos_my,
|
|
at: pos_at,
|
|
of: $event,
|
|
collision: "flip flip"
|
|
});
|
|
|
|
} // eof switch
|
|
|
|
|
|
if ($.fn.jScrollPane) {
|
|
$(".jscroll").jScrollPane();
|
|
}
|
|
|
|
}, // eof eventModal
|
|
|
|
|
|
|
|
legendModal : function (id) {
|
|
|
|
var me=this;
|
|
|
|
var leg = [];
|
|
|
|
if (id=="pres" || timeglider.mode == "presentation") {
|
|
|
|
if (typeof MED.presentation.legend == "object") {
|
|
leg = MED.presentation.legend;
|
|
}
|
|
} else {
|
|
leg = MED.timelineCollection.get(id).attributes.legend;
|
|
}
|
|
|
|
var html = "",
|
|
i_sel = "",
|
|
initClass = "";
|
|
|
|
if (options.legend.type === "checkboxes") {
|
|
initClass = "tg-legend-item tg-legend-icon-selected";
|
|
} else {
|
|
initClass = "tg-legend-item";
|
|
|
|
}
|
|
|
|
_.each(leg, function(l){
|
|
html += "<li class='" + initClass + "'><img class='tg-legend-icon' src='" + options.icon_folder + l.icon + "'><span class='legend-info'>" + l.title + "</span></li>";
|
|
});
|
|
|
|
var templ_obj = {id:id, legend_list:html};
|
|
|
|
// remove existing legend
|
|
$(CONTAINER + " .tg-legend").remove();
|
|
|
|
$.tmpl(me._templates.legend_modal,templ_obj)
|
|
.appendTo(CONTAINER)
|
|
.css("z-index", me.ztop++)
|
|
.toggleClass("tg-display-none")
|
|
.position({
|
|
my: "right-64 top+38",
|
|
at: "right top",
|
|
of: $(CONTAINER),
|
|
collision: "none"
|
|
});
|
|
|
|
i_sel = CONTAINER + " .legend-info, " + CONTAINER + " .tg-legend-item";
|
|
|
|
$(CONTAINER).delegate(".tg-legend-item", "mouseup", function(e) {
|
|
|
|
|
|
var $legend_item = $(this);
|
|
|
|
var icon = ($legend_item.children("img").attr("src"));
|
|
$legend_item.toggleClass("tg-legend-icon-selected");
|
|
MED.setFilters({origin:"legend", icon: icon});
|
|
});
|
|
|
|
|
|
// selects all legend items so that
|
|
// only legend-related items are showing on
|
|
// the stage
|
|
if (options.legend.type === "checkboxes") {
|
|
setTimeout(function() {
|
|
$(".tg-legend-all").trigger("click");
|
|
}, 200);
|
|
}
|
|
},
|
|
|
|
|
|
|
|
parseHTMLTable : function(table_id) {
|
|
var obj = {},
|
|
now = +new Date(),
|
|
keys = [];
|
|
|
|
$('#' + table_id).find('tr').each(function(i){
|
|
////////// each..
|
|
var children = $(this).children(),
|
|
row_obj;
|
|
|
|
if ( i === 0 ) {
|
|
keys = children.map(function(){
|
|
return $(this).attr( 'class' ).replace( /^.*?\btg-(\S+)\b.*?$/, '$1' );
|
|
}).get();
|
|
|
|
} else {
|
|
row_obj = {};
|
|
|
|
children.each(function(i){
|
|
row_obj[ keys[i] ] = $(this).text();
|
|
});
|
|
|
|
obj[ 'prefix' + now++ ] = row_obj;
|
|
}
|
|
/////////
|
|
});
|
|
return obj;
|
|
|
|
}
|
|
|
|
} // end VIEW prototype
|
|
|
|
|
|
|
|
tg.TG_TimelineView = Backbone.View.extend({
|
|
|
|
initialize: function (t) {
|
|
|
|
var me=this;
|
|
|
|
this.mediator = t.model.get("mediator");
|
|
|
|
this.model.bind('change:title', function () {
|
|
$(me.el).find(".timeline-title-span").text(me.model.get("title"));
|
|
});
|
|
|
|
if (this.mediator.timelineCollection.length > 1 || tg.mode == "authoring") {
|
|
this.titleBar = "fullBar";
|
|
} else {
|
|
this.titleBar = "hiddenBar";
|
|
}
|
|
|
|
this.model.bind('destroy', this.remove, this);
|
|
},
|
|
|
|
|
|
tagName: "div",
|
|
|
|
events: {
|
|
"click .timeline-title-span" : "titleClick"
|
|
},
|
|
|
|
className: "tg-timeline-envelope clearfix",
|
|
|
|
|
|
getTemplate: function() {
|
|
|
|
var tmpl = "",
|
|
env_bts = "",
|
|
env_b = "",
|
|
inverted = "";
|
|
|
|
|
|
if (this.model.get("inverted")) {
|
|
inverted = " timeline-inverted";
|
|
|
|
} else {
|
|
inverted = "";
|
|
}
|
|
|
|
debug.log("titlebar:", this.titleBar);
|
|
|
|
if (this.titleBar == "fullBar") {
|
|
|
|
tmpl = "<div class='titleBar'>"
|
|
+ "<div class='timeline-title" + inverted + "'>"
|
|
+ "<span class='timeline-title-span'>";
|
|
|
|
|
|
|
|
|
|
env_bts = "<div class='tg-env-buttons'>";
|
|
|
|
// INFO BUTTON
|
|
if (this.model.get("description")) {
|
|
env_bts += "<div class='tg-env-button tg-env-info timeline-info-bt' data-timeline_id='${id}'></div>";
|
|
}
|
|
|
|
// LEGEND BUTTON
|
|
if (this.model.get("hasLegend")) {
|
|
env_bts += "<div class='tg-env-button tg-env-legend tg-legend-bt' data-timeline_id='${id}'></div>";
|
|
}
|
|
|
|
// INVERT BUTTON
|
|
env_bts += "<div class='tg-env-button tg-env-invert tg-invert-bt' data-timeline_id='${id}'></div>";
|
|
|
|
// EXPAND BUTTON
|
|
env_bts += "<div class='tg-env-button tg-env-expcol tg-expcol-bt' data-timeline_id='${id}'></div>";
|
|
|
|
env_bts += "</div>";
|
|
|
|
|
|
// env_b = (timeglider.mode == "preview" || timeglider.mode == "publish") ? env_bts : "";
|
|
|
|
tmpl += env_bts + "${title}</span></div></div>";
|
|
|
|
|
|
} else if (this.titleBar == "imageBar") {
|
|
tmpl = "<div class='titleBar imageBar'></div>";
|
|
} else {
|
|
tmpl = "<div class='titleBar tg-display-none'></div>";
|
|
}
|
|
|
|
return tmpl;
|
|
},
|
|
|
|
render: function() {
|
|
|
|
var me = this;
|
|
var id = me.model.get("id");
|
|
var title = me.model.get("title");
|
|
|
|
var _template = me.getTemplate();
|
|
|
|
var state_class = this.model.get("inverted") ? "inverted" : "straight-up";
|
|
|
|
$(this.el)
|
|
.html($.tmpl(_template, this.model.attributes))
|
|
.attr("id", this.model.get("id"))
|
|
.addClass(state_class);
|
|
|
|
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
setText: function() {
|
|
/*
|
|
var text = this.model.get('text');
|
|
this.$('.todo-text').text(text);
|
|
this.input = this.$('.todo-input');
|
|
*/
|
|
},
|
|
|
|
|
|
titleClick: function() {
|
|
MED.timelineTitleClick(this.model.get("id"));
|
|
},
|
|
|
|
|
|
remove: function() {
|
|
$(this.el).remove();
|
|
}
|
|
|
|
|
|
//clear: function() {
|
|
// this.model.destroy();
|
|
//}
|
|
|
|
});
|
|
|
|
/*
|
|
zoomTree
|
|
****************
|
|
there's no zoom level of 0, so we create an empty element @ 0
|
|
|
|
This could eventually be a more flexible system so that a 1-100
|
|
value-scale could apply not to "5 hours to 10 billion years", but
|
|
rather to 1 month to 10 years. For now, it's static according to
|
|
a "universal" system.
|
|
*/
|
|
|
|
tg.zoomTree = [
|
|
{},
|
|
{unit:"da", width:35000,level:1, label:"30 minutes"},
|
|
{unit:"da", width:17600,level:2, label:"1 hour"},
|
|
{unit:"da", width:8800,level:3, label:"2 hours"},
|
|
{unit:"da", width:4400,level:4, label:"5 hours"},
|
|
{unit:"da", width:2200, level:5, label:"10 hours"},
|
|
{unit:"da", width:1100, level:6, label:"1 DAY"},
|
|
{unit:"da", width:550, level:7, label:"40 hours"},
|
|
{unit:"da", width:432, level:8, label:"2 days"},
|
|
{unit:"da", width:343, level:9, label:"2.5 days"},
|
|
{unit:"da", width:272, level:10, label:"3 days"},
|
|
{unit:"da", width:216, level:11, label:"4 days"},
|
|
{unit:"da", width:171, level:12, label:"5 days"},
|
|
{unit:"da", width:136, level:13, label:"1 WEEK"},
|
|
{unit:"da", width:108, level:14, label:"8 days"},
|
|
/* 108 * 30 = equiv to a 3240 month */
|
|
{unit:"mo", width:2509, level:15, label:"10 days"},
|
|
{unit:"mo", width:1945, level:16, label:"2 WEEKS"},
|
|
{unit:"mo", width:1508, level:17, label:"18 days"},
|
|
{unit:"mo", width:1169, level:18, label:"3 weeks"},
|
|
{unit:"mo", width:913, level:19, label:"1 MONTH"},
|
|
{unit:"mo", width:719, level:20, label:"5 weeks"},
|
|
{unit:"mo", width:566, level:21, label:"6 weeks"},
|
|
{unit:"mo", width:453, level:22, label:"2 MONTHS"},
|
|
|
|
|
|
{unit:"mo", width:362, level:23, label:"10 weeks"},
|
|
{unit:"mo", width:290, level:24, label:"3 MONTHS"},
|
|
{unit:"mo", width:232, level:25, label:"4 months"},
|
|
{unit:"mo", width:186, level:26, label:"5 months"},
|
|
{unit:"mo", width:148, level:27, label:"6 MONTHS"},
|
|
{unit:"mo", width:119, level:28, label:"7 months"},
|
|
{unit:"mo", width:95, level:29, label:"9 months"},
|
|
{unit:"mo", width:76, level:30, label:"1 YEAR"},
|
|
/* 76 * 12 = equiv to a 912 year */
|
|
{unit:"ye", width:723, level:31, label:"15 months"},
|
|
{unit:"ye", width:573, level:32, label:"18 months"},
|
|
{unit:"ye", width:455, level:33, label:"2 YEARS"},
|
|
{unit:"ye", width:361, level:34, label:"2.5 years"},
|
|
{unit:"ye", width:286, level:35, label:"3 years"},
|
|
{unit:"ye", width:227, level:36, label:"4 years"},
|
|
{unit:"ye", width:179, level:37, label:"5 years"},
|
|
{unit:"ye", width:142, level:38, label:"6 years"},
|
|
{unit:"ye", width:113, level:39, label:"8 years"},
|
|
{unit:"ye", width:89, level:40, label:"10 years"},
|
|
{unit:"de", width:705, level:41, label:"13 years"},
|
|
{unit:"de", width:559, level:42, label:"16 years"},
|
|
{unit:"de", width:443, level:43, label:"20 years"},
|
|
|
|
{unit:"de", width:302, level:44, label:"25 years"},
|
|
{unit:"de", width:240, level:45, label:"30 years"},
|
|
{unit:"de", width:190, level:46, label:"40 years"},
|
|
{unit:"de", width:150, level:47, label:"50 years"},
|
|
{unit:"de", width:120, level:48, label:"65 years"},
|
|
{unit:"de", width:95, level:49, label:"80 years"},
|
|
{unit:"de", width:76, level:50, label:"100 YEARS"},
|
|
{unit:"ce", width:600, level:51, label:"130 years"},
|
|
{unit:"ce", width:480, level:52, label:"160 years"},
|
|
{unit:"ce", width:381, level:53, label:"200 YEARS"},
|
|
{unit:"ce", width:302, level:54, label:"250 years"},
|
|
{unit:"ce", width:240, level:55, label:"300 years"},
|
|
{unit:"ce", width:190, level:56, label:"400 years"},
|
|
{unit:"ce", width:150, level:57, label:"500 YEARS"},
|
|
{unit:"ce", width:120, level:58, label:"600 years"},
|
|
{unit:"ce", width:95, level:59, label:"1000 YEARS"},
|
|
{unit:"ce", width:76, level:60, label:"1100 years"},
|
|
{unit:"thou", width:603, level:61, label:"1500 years"},
|
|
|
|
{unit:"thou", width:478, level:62, label:"2000 years"},
|
|
{unit:"thou", width:379, level:63, label:"2500 years"},
|
|
{unit:"thou", width:301, level:64, label:"3000 years"},
|
|
{unit:"thou", width:239, level:65, label:"4000 years"},
|
|
{unit:"thou", width:190, level:66, label:"5000 YEARS"},
|
|
{unit:"thou", width:150, level:67, label:"6000 years"},
|
|
{unit:"thou", width:120, level:68, label:"7500 years"},
|
|
{unit:"thou", width:95, level:69, label:"10,000 YEARS"},
|
|
{unit:"thou", width:76, level:70, label:"12,000 years"},
|
|
{unit:"tenthou", width:603, level:71, label:"15,000 years"},
|
|
{unit:"tenthou", width:358, level:72, label:"25,000 years"},
|
|
{unit:"tenthou", width:213, level:73, label:"40,000 years"},
|
|
{unit:"tenthou", width:126, level:74, label:"70,000 years"},
|
|
{unit:"tenthou", width:76, level:75, label:"100,000 YEARS"},
|
|
{unit:"hundredthou", width:603, level:76, label:"150,000 years"},
|
|
{unit:"hundredthou", width:358, level:77, label:"250,000 years"},
|
|
{unit:"hundredthou", width:213, level:78, label:"400,000 years"},
|
|
{unit:"hundredthou", width:126, level:79, label:"700,000 years"},
|
|
{unit:"hundredthou", width:76, level:80, label:"1 million years"},
|
|
{unit:"mill", width:603, level:81, label:"1.5 million years"},
|
|
{unit:"mill", width:358, level:82, label:"3 million years"},
|
|
{unit:"mill", width:213, level:83, label:"4 million years"},
|
|
{unit:"mill", width:126, level:84, label:"6 million years"},
|
|
{unit:"mill", width:76, level:85, label:"10 million years"},
|
|
{unit:"tenmill", width:603, level:86, label:"15 million years"},
|
|
{unit:"tenmill", width:358, level:87, label:"25 million years"},
|
|
{unit:"tenmill", width:213, level:88, label:"40 million years"},
|
|
{unit:"tenmill", width:126, level:89, label:"70 million years"},
|
|
{unit:"tenmill", width:76, level:90, label:"100 million years"},
|
|
{unit:"hundredmill", width:603, level:91, label:"120 million years"},
|
|
{unit:"hundredmill", width:358, level:92, label:"200 million years"},
|
|
{unit:"hundredmill", width:213, level:93, label:"300 million years"},
|
|
{unit:"hundredmill", width:126, level:94, label:"500 million years"},
|
|
{unit:"hundredmill", width:76, level:95, label:"1 billion years"},
|
|
{unit:"bill", width:603, level:96, label:"15 million years"},
|
|
{unit:"bill", width:358, level:97, label:"30 million years"},
|
|
{unit:"bill", width:213, level:98, label:"50 million years"},
|
|
{unit:"bill", width:126, level:99, label:"80 million years"},
|
|
{unit:"bill", width:76, level:100, label:"100 billion years"}
|
|
];
|
|
|
|
// immediately invokes to create extra information in zoom tree
|
|
//
|
|
tg.calculateSecPerPx = function (zt) {
|
|
for (var z=1; z<zt.length; z++) {
|
|
var zl = zt[z];
|
|
var sec = 0;
|
|
switch(zl.unit) {
|
|
case "da": sec = 86400; break;
|
|
case "mo": sec = 2419200; break; // assumes only 28 days per
|
|
case "ye": sec = 31536000; break;
|
|
case "de": sec = 315360000; break;
|
|
case "ce": sec = 3153600000; break;
|
|
case "thou": sec = 31536000000; break;
|
|
case "tenthou": sec = 315360000000; break;
|
|
case "hundredthou": sec = 3153600000000; break;
|
|
case "mill": sec = 31536000000000; break;
|
|
case "tenmill": sec = 315360000000000; break;
|
|
case "hundredmill": sec = 3153600000000000; break;
|
|
case "bill": sec = 31536000000000000; break;
|
|
}
|
|
// generate hash for seconds per pixel
|
|
zl.spp = Math.round(sec / parseInt(zl.width));
|
|
|
|
}
|
|
|
|
// call it right away to establish values
|
|
}(tg.zoomTree); // end of zoomTree
|
|
|
|
|
|
/* a div with id of "hiddenDiv" has to be pre-loaded */
|
|
tg.getStringWidth = function (str) {
|
|
|
|
var $ms = $("#timeglider-measure-span");
|
|
$ms.css("font-size", MED.base_font_size);
|
|
|
|
if (str) {
|
|
// for good measure, make it a touch larger
|
|
return $ms.html(str).width() + MED.base_font_size;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
tg.scaleToImportance = function(imp, zoom_level) {
|
|
// flash version: return ((importance - zoomLev) * 4.5) + 100;
|
|
// 100 being 1:1 or 12 px
|
|
|
|
// first basic version: return imp / zoo;
|
|
|
|
return (((imp - zoom_level) * 4.5) + 100) / 100;
|
|
},
|
|
|
|
|
|
|
|
|
|
String.prototype.removeWhitespace = function () {
|
|
var rg = new RegExp( "\\n", "g" )
|
|
return this.replace(rg, "");
|
|
}
|
|
|
|
|
|
|
|
if (debug) {
|
|
// adding a screen display for anything needed
|
|
debug.trace = function (stuff, goes) {
|
|
$("#" + goes).text(stuff);
|
|
}
|
|
}
|
|
|
|
|
|
tg.googleMapsInit = function () {
|
|
// debug.log("initializing google maps...")
|
|
}
|
|
|
|
tg.googleMapsLoaded = false;
|
|
tg.googleMapsLoad = function () {
|
|
|
|
|
|
if (tg.googleMapsLoaded == false) {
|
|
|
|
var script = document.createElement('script');
|
|
script.type = 'text/javascript';
|
|
script.src = 'http://maps.googleapis.com/maps/api/js?sensor=false&' +
|
|
'callback=timeglider.googleMapsInit';
|
|
document.body.appendChild(script);
|
|
|
|
tg.googleMapsLoaded = true;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
})(timeglider);
|