/* * 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: "
" + "
" + "
{{html dateline}}
" + "

${title}

" + "

{{html image}}{{html description}}

" + "" + "
", // For displaying an exterior page directly in the modal event_modal_iframe: "
" + "
" + "
{{html dateline}}
" + "

{{html title}}

" + "" + "
", // generated, appended on the fly, then removed event_modal_full : $.template( null, //////// "
" + "
" + "
" + "
" + "
" + "" + "

${title}

" + "
" + "{{html image}}{{html description}}" // + "
" + "
" + "" // end of modal + "
"), // generated, appended on the fly, then removed filter_modal : $.template( null, "
"+ "
"+ "

search/filter

"+ "
"+ "
"+ "
"+ "
"+ ""+ "   "+ "
"+ "
"+ ""+ "
"+ // "
hide: "+ // "
"+ "
    "+ "
  • go
  • "+ "
  • clear
  • "+ "
"+ "
"+ "
"), timeline_list_modal : $.template( null, "
"+ "
"+ "

timelines

"+ "
    "+ "
    "+ "
    "), settings_modal : $.template( null, "
    "+ "
    "+ "

    settings

    "+ "
    "+ "
    "+ "
    "+ "
    "), legend_modal : $.template( null, "
    "+ "
    "+ "
      {{html legend_list}}
    "+ "
    all | none
    "+ "
    "+ "
    ") }; 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 = "
  • "; _.each(tl_tags, function(val, key) { thtm.push(key + " (" + val + ")"); }); tags_intro = "

    Use the search tool (at lower right) to filter this timeline according to these tags:

    "; tags2 = "
    " + tags_intro + thtm.join(", ") + "
    " } return "

    ${title}

    " + "
    " + "
    {{html description}}
    " + "
      " + tags1 + "
    • start
    " + tags2 + "
    "; }, 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 "
    {{html description}}
    " + "
    • close
    • start
    " + "
    "; }, 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 "
    " + "

    Go to...

    " + "
    " + "
    " + "" + "
    go
    " + "
    " + "
    " + "
    "; }, 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("
    start zoom:" + MED.gestureStartZoom); // This basically works, but it's funky still.... var g = Math.ceil(MED.gestureStartZoom / (e.scale / 2)); //$("#output").append("
    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("
    gesture zoom:" + MED.getZoomLevel()); }, false); tgcompnt.addEventListener("gestureend", function (e) { e.preventDefault(); $("#output").append("
    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 = $("").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
  • 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 = ""; 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('timezone: ' + tz_menu + '
    save'); $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 = "

    " + timeline.get("title") + "

    "; inf = (timeline.get("description")) ? "
  • info
  • ":"", leg = (timeline.get("hasLegend")) ? "
  • legend
  • ":"", tools = ""; // "tools", tmpl = "
    • start
    "; $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 = "

    " + pres.title + "

    ", inf = (pres.description) ? "
  • info
  • ":"", leg = (pres.legend) ? "
  • legend
  • ":"", tools = "", // "tools", tmpl = "
    " + title + "
      " + inf + leg + "
    • start
    " + tools + "
    ", $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 = $("
    ").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 = $("
    ").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 = "", endDateF = ""; if (ev.span == true) { endDateF = " – "; } 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("
    ").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 = "
    " + ev.title + "
    "; } 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("
    "); // 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= $("
    ") .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("
    " + sub_label + "
    "); 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("
    " + sub_labels + "
    "); } 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 "
    " + htxt + ":00 " + ampm + "
    " + "
    " + htxt + ":15 " + ampm + "
    " + "
    " + htxt + ":30 " + ampm + "
    " + "
    " + htxt + ":45 " + ampm + "
    "; } 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 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" + lane.title + "
    "); $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 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 = $("
      ").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 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=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 += "
      "; } } // 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 = "
    • link
    • " } else if (typeof(linkage) == "object"){ // array of links with labels and urls for (l=0; l" + lLab + "" } } 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) ? "" : "", 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 = $("
      ").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
      ").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 += "
    • " + l.title + "
    • "; }); 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 = "
      " + "
      " + ""; env_bts = "
      "; // INFO BUTTON if (this.model.get("description")) { env_bts += "
      "; } // LEGEND BUTTON if (this.model.get("hasLegend")) { env_bts += "
      "; } // INVERT BUTTON env_bts += "
      "; // EXPAND BUTTON env_bts += "
      "; env_bts += "
      "; // env_b = (timeglider.mode == "preview" || timeglider.mode == "publish") ? env_bts : ""; tmpl += env_bts + "${title}
      "; } else if (this.titleBar == "imageBar") { tmpl = "
      "; } else { tmpl = "
      "; } 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