/public/javascripts/timeglider/TG_Mediator.js
JavaScript | 645 lines | 348 code | 150 blank | 147 comment | 79 complexity | d201f7126207210dbe6ce3a802479f85 MD5 | raw file
- /*!
- * Timeglider for Javascript / jQuery
- * http://timeglider.com/jquery
- *
- * Copyright 2011, Mnemograph LLC
- * Licensed under the MIT open source license
- * http://timeglider.com/jquery/?p=license
- *
- */
- /*******************************
- TIMELINE MEDIATOR
- handles timeline behavior,
- reflects state back to view
- ********************************/
- (function(tg){
-
-
- var MED = {},
- TG_Date = tg.TG_Date,
- options = {},
- $ = jQuery,
- contants = {
-
-
- }
-
- /* In progress... */
- tg.TimelineCollection = Backbone.Collection.extend({
- model: timeglider.TG_Timeline
- });
-
- tg.TG_Mediator = function (wopts) {
-
- this.options = options = wopts;
-
- // quasi-private vars
- this._focusDate = {};
- this._zoomInfo = {};
- this._zoomLevel = 1;
-
- this.ticksReady = false;
- this.ticksArray = [];
- this.startSec = 0;
- this.activeTimelines = [];
- this.max_zoom = options.max_zoom;
- this.min_zoom = options.min_zoom;
- this.fixed_zoom = (this.max_zoom == this.min_zoom) ? true : false;
- this.gesturing = false;
- this.gestureStartZoom = 0;
- this.gestureStartScale = 0; // .999 etc reduced to 1 to 100
- this.filters = {include:"", exclude:"", legend:[]};
- this.eventPool = [];
- // this.eventPool['ev_000'] = "hello!";
-
- this.timelinePool = {};
- this.imagesSized = 0;
- this.imagesToSize = 0;
- this.timelineDataLoaded = false,
- this.foo = "bark";
-
- // this.setZoomLevel(options.initial_zoom);
- this.initial_timeline_id = options.initial_timeline_id || "";
- this.sole_timeline_id = "";
-
- if (options.max_zoom === options.min_zoom) {
- this.fixed_zoom = options.min_zoom;
- }
-
- MED = this;
- } // end mediator head
-
- tg.TG_Mediator.prototype = {
-
- /* PUBLIC METHODS MEDIATED BY $.widget front */
- gotoDate : function (fdStr) {
- var fd = new TG_Date(fdStr);
- this.setFocusDate(fd);
- // setting date doesn't by itself refresh: do it "manually"
- this.refresh();
- return true;
- },
- gotoDateZoom : function (fdStr, zoom) {
- var fd = new TG_Date(fdStr);
- this.setFocusDate(fd);
- // setting zoom _does_ refresh automatically
- var zlc = this.setZoomLevel(zoom);
-
- if (zlc == false) { this.refresh(); }
-
- },
-
- zoom : function (n) {
- var new_zoom = this.getZoomLevel() + parseInt(n);
- debug.log("mediator zoom:" + new_zoom);
- this.setZoomLevel(new_zoom);
- },
-
-
-
- /*
- * loadTimelineData
- * @param src {object} object OR json data to be parsed for loading
- * TODO: create option for XML input
- */
- loadTimelineData : function (src) {
- var M = this; // model ref
- // Allow to pass in either the url for the data or the data itself.
-
- if (src) {
-
- debug.log("HELLO??");
- if (typeof src === "object") {
- // local/pre-loaded JSON
- M.parseData(src);
- } else if (src.substr(0,1) == "#") {
-
- debug.log("IT'S A TABLE!");
- var tableData = [M.getTableTimelineData(src)];
- // debug.log(JSON.stringify(tableData));
- M.parseData(tableData);
- } else {
- $.getJSON(src, function (data) {
- M.parseData(data);
- }
- );
- }// end [obj vs remote]
-
- } else {
- // NO INITIAL DATA: That's cool, still build the timeline
- // focusdate has been set to today
- this.timelineDataLoaded = true;
- this.setZoomLevel(Math.floor((this.max_zoom + this.min_zoom) / 2));
- this.tryLoading();
-
- }
-
- },
-
- /*
- * getTableTimelineData
- * @param table_id {string} the html/DOM id of the table
- * @return timeline data object ready for parsing
- *
- */
- getTableTimelineData : function (table_id) {
- var tl = {},
- now = 0,
- keys = [], field, value,
- event_id = '',
- $table = $(table_id);
- // timeline head
- tl.id = table_id.substr(1);
- tl.title = $table.attr("title") || "untitled";
- tl.description = $table.attr("description") || "";
- tl.focus_date = $table.attr("focus_date") || TG_Date.getToday;
- tl.initial_zoom = $table.attr("initial_zoom") || 20;
- tl.events = [];
- $table.find('tr').each(function(i){
- var children = $(this).children(),
- row_obj;
- // first row -- <th> or <td>, gather the field names
- if ( i === 0 ) {
- keys = children.map(function(){
- // using "tg-*" map each column to the corresponding data
- return $(this).attr( 'class' ).replace( /^.*?\btg-(\S+)\b.*?$/, '$1' );
- }).get();
- } else {
- // i.e. an event
- row_obj = {};
- children.each(function(i){
- field = keys[i],
- value = $(this).text();
- // TODO: VALIDATE EVENT STUFF HERE
- row_obj[ field ] = value;
- });
- event_id = 'ev_' + now++;
- row_obj.id = event_id;
- tl.events.push(row_obj);
- } // end if-else i===0
- }); // end .each()
-
- $table.css("display", "none");
- return tl;
- },
-
- /*
- * parseData
- * @param data {object} Multiple (1+) timelines object derived from data in loadTimelineData
- */
- parseData : function (data) {
-
- var M = this; // model ref
- var ct = 0;
- var dl = data.length, ti = {}, ondeck = {};
-
- for (var i=0; i<dl;i++) {
-
- ondeck = data[i];
- ondeck.mediator = M;
- ti = new timeglider.TG_Timeline(ondeck).toJSON(); // the timeline
- if (ti.id.length > 0) {
-
- ct++;
- M.swallowTimeline(ti);
- }
-
- }
- if (ct === 0) {
- alert("ERROR loading data: Check JSON with jsonLint");
- } else {
-
- this.timelineDataLoaded = true;
- // might as well try!
- this.tryLoading();
- }
- },
-
- /*
- * tryLoading
- * Sees if all criteria for proceeding to display the loaded data
- * are complete: data, image sizeing and others
- *
- */
- tryLoading : function () {
-
- var a = (this.imagesSized == this.imagesToSize),
- b = (this.timelineDataLoaded == true);
-
- if (a && b) {
- this.setInitialTimelines();
-
- $.publish("mediator.timelineDataLoaded");
- }
-
-
- },
- /* Makes an indexed array of timelines */
- swallowTimeline : function (obj) {
- this.sole_timeline_id = obj.id;
- this.timelinePool[obj.id] = obj;
- $.publish("mediator.timelineListChangeSignal");
- },
- /// end of methods that need to go into (backbone) data model
- ///////////////////////////
- /*
- TODO: turn to $each, adding to activeTimelines:
- i.e. could be more than one
- */
- setInitialTimelines : function () {
- var me = this;
- var tid = this.initial_timeline_id || this.sole_timeline_id;
- debug.log("TID:" + tid);
- if (tid) {
- setTimeout(function () {
- MED.toggleTimeline(tid);
- }, 1000);
- }
- },
- refresh : function () {
- this.startSec = this._focusDate.sec;
- $.publish("mediator.refreshSignal");
- },
- // !!!TODO ---- get these back to normal setTicksReady, etc.
- setTicksReady : function (bool) {
- this.ticksReady = bool;
-
- if (bool === true) {
- $.publish("mediator.ticksReadySignal");
- }
- },
- getFocusDate : function () {
- return this._focusDate;
- },
-
- /*
- * setFocusDate
- * @param fd [TG_Date instance]
- *
- */
- setFocusDate : function (fd) {
- // !TODO :: VALIDATE FOCUS DATE
-
- if (fd != this._focusDate) {
- this._focusDate = fd;
- debug.trace("setting fd:" + fd.dateStr, "note");
- }
- },
-
- /*
- * getZoomLevel
- * @return {Number} zoom level number from 1 to 100
- *
- *
- *
- */
- getZoomLevel : function () {
- return parseInt(this._zoomLevel);
- },
-
- /*
- * setZoomLevel
- * This in turn sets other zoomInfo attributes : width, label, tickWidth
- * Other zoom info comes from the zoomTree array
- * @param z ==> integer from 1-100
- *
- */
- setZoomLevel : function (z) {
-
- if (z <= this.max_zoom && z >= this.min_zoom) {
-
- // focusdate has to come first for combined zoom+focusdate switch
- this.startSec = this._focusDate.sec;
-
- if (z != this._zoomLevel) {
- this._zoomLevel = z;
- this._zoomInfo = timeglider.zoomTree[z];
- $.publish("mediator.zoomLevelChange");
- return true
- } else {
- return false;
- }
- // end min/max check
- } else { return false; }
- },
- /*
- * getZoomInfo
- * @return obj {Object} with
- * zoomLevel (Number), label (String), tickWidth (Number), unit (String)
- *
- */
- getZoomInfo : function () {
- return this._zoomInfo;
- },
-
-
-
- /*
- * setFilters
- * @param obj {Object} containing:
- * origin ("clude", "legend"), include (Str), exclude (Str), legend (Obj)
- *
- */
- setFilters : function (obj) {
-
- switch (obj.origin) {
-
- case "clude":
- this.filters.include = obj.include;
- this.filters.exclude = obj.exclude;
- break;
-
- case "legend":
-
- var icon = obj.icon;
-
- if (icon == "all") {
- this.filters.legend = [];
- $.publish("mediator.legendAll");
- } else {
-
- if ($.inArray(icon, this.filters.legend) == -1) {
- this.filters.legend.push(icon);
- } else {
- // remove it
- var fol = this.filters.legend;
- var fr = [];
- fr = $.grep(fol, function (a) { return a != icon; });
- this.filters.legend = fr;
- }
-
- } // end if/else for "clear"
-
- break;
-
- } // end switch
-
- $.publish("mediator.filtersChange");
- this.refresh();
- },
-
- getTicksOffset : function () {
- return this._ticksOffset;
- },
- setTicksOffset : function (newOffset) {
- // This triggers changing the focus date
- // main listener hub for date focus and tick-appending
- this._ticksOffset = newOffset;
- /* In other words, ticks are being dragged! */
- $.publish( "mediator.ticksOffsetChange" );
- },
- /*
- * getTickBySerial
- * @param serial {Number} serial date unit number (rata die, monthnum, year, etc)
- *
- * @return {Object} info about _existing_ displayed tick
- *
- */
- getTickBySerial : function (serial) {
- var ta = this.ticksArray,
- tal = ta.length;
- for (var t=0; t<tal; t++) {
- var tick = ta[t];
- if (tick.serial == serial) { return tick; }
- }
- return false;
- },
- /*
- * addToTicksArray
- * @param obj {Object}
- * serial: #initial tick
- * type:init|l|r
- * unit:ye | mo | da | etc
- * width: #px
- * left: #px
- * @param focusDate {TG_Date}
- * used for initial tick; others set off init
- */
- addToTicksArray : function (obj, focusDate) {
- if (obj.type == "init") {
- // CENTER
- obj.serial = TG_Date.getTimeUnitSerial(focusDate, obj.unit);
- this.ticksArray = [obj];
- } else if (obj.type == "l") {
- // LEFT
- obj.serial = this.ticksArray[0].serial - 1;
- this.ticksArray.unshift(obj);
- } else {
- // RIGHT SIDE
- obj.serial = this.ticksArray[this.ticksArray.length -1].serial + 1;
- this.ticksArray.push(obj);
- }
- // this.ticksArrayChange.broadcast();
- $.publish( "mediator.ticksArrayChange" );
-
- return obj.serial;
- },
- toggleTimeline : function (id) {
-
- var lt = this.timelinePool[id];
- var ia = $.inArray(id, this.activeTimelines);
- if (ia == -1) {
- // The timeline is
- // not active ---- bring it on
- this.activeTimelines.push(id);
- // setting FD does NOT refresh
-
- // timeline focus_date is ISO-8601 basic
- // ==== new TG_Date()
- var tl_fd = new TG_Date(lt.focus_date);
-
- this.setFocusDate(tl_fd);
-
- // resetting zoomLevel DOES refresh
- this.setZoomLevel(lt.initial_zoom);
-
- } else {
- // it's active, remove it
- this.activeTimelines.splice(ia,1);
- }
-
- this.refresh();
- // this will change the menu list/appearance
- $.publish( "mediator.activeTimelinesChange" );
- },
-
- /*
- * reportImageSize
- * @param img {Object} has "id" of event, "src", "width" and "height" at least
- *
- * This information is reported from TG_Timeline as data is loading. Since image
- * size gathering sidetracks from data loading, there's a
- *
-
- */
- reportImageSize : function (img) {
-
- var ev = MED.eventPool["ev_" + img.id];
-
- if (!img.error) {
- ev.image.width = img.width;
- ev.image.height = img.height;
- } else {
- ev.image = {}
- debug.log("WHOOPS: MISSING IMAGE: " + img.src);
- }
- this.imagesSized++;
-
- if (this.imagesSized == this.imagesToSize) {
- // if there are images, this would usually be
- // the last step before proceeding
- this.tryLoading();
- }
- }
- ///// end model prototype object
- };
-
-
- tg.getLowHigh = function (arr) {
- var i, n,
- high = parseFloat(arr[0]),
- low = high;
- for (i=0; i<arr.length; i++) {
- n = parseFloat(arr[i]);
- if (n<low) low = n;
- if (n>high) high = n;
- }
- return {"high":high, "low":low}
- };
-
-
-
-
-
- tg.validateOptions = function (widget_settings) {
-
- this.optionsMaster = { initial_focus:{type:"date"},
- editor:{type:"string"},
- backgroundColor:{type:"color"},
- backgroundImage:{type:"color"},
- min_zoom:{type:"number", min:1, max:100},
- max_zoom:{type:"number", min:1, max:100},
- initial_zoom:{type:"number", min:1, max:100},
- show_centerline:{type:"boolean"},
- display_zoom_level:{type:"boolean"},
- data_source:{type:"url"},
- basic_fontsize:{type:"number", min:9, max:100},
- mouse_wheel:{type:"string", possible:["zoom","pan"]},
- initial_timeline_id:{type:"string"},
- icon_folder:{type:"string"},
- show_footer:{type:"boolean"},
- display_zoom_level:{type:"boolean"}
- }
-
- // msg: will be return value: validates when empty
- // change lb to <br> if the error is returned in HTML (vs alert())
- var me = this, msg = "", lb = "\n";
- $.each(widget_settings, function(key, value) {
- if (me.optionsMaster[key]) {
- switch (me.optionsMaster[key].type) {
- case "string":
- if (typeof value != "string") { msg += (key + " needs to be a string." + lb); }
- if (me.optionsMaster[key].possible) {
- if ($.inArray(value, me.optionsMaster[key].possible) == -1) {
- msg += (key + " must be: " + me.optionsMaster[key].possible.join(" or "));
- }
- }
- break;
- case "number":
- if (typeof value != "number") { msg += (value + " needs to be a number." + lb); }
- if (me.optionsMaster[key].min) {
- if (value < me.optionsMaster[key].min) {
- msg += (key + " must be greater than or equal to " + me.optionsMaster[key].min + lb);
- }
- }
- if (me.optionsMaster[key].max) {
- if (value > me.optionsMaster[key].max) {
- msg += (key + " must be less than or equal to " + me.optionsMaster[key].max + lb);
- }
- }
- break;
- case "date":
- // TODO validate a date string using TG_Date...
- break;
- case "boolean":
- if (typeof value != "boolean") msg += (value + " needs to be a number." + lb);
- break;
- case "url":
- // TODO test for pattern for url....
- break;
- case "color":
- /// TODO test for pattern for color, including "red", "orange", etc
- break;
- }
- }
- }); // end each
- return msg;
- };
-
-
- })(timeglider);