PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/public/javascripts/timeglider/TG_Mediator.js

https://github.com/bhennes2/storygram
JavaScript | 645 lines | 348 code | 150 blank | 147 comment | 79 complexity | d201f7126207210dbe6ce3a802479f85 MD5 | raw file
  1. /*!
  2. * Timeglider for Javascript / jQuery
  3. * http://timeglider.com/jquery
  4. *
  5. * Copyright 2011, Mnemograph LLC
  6. * Licensed under the MIT open source license
  7. * http://timeglider.com/jquery/?p=license
  8. *
  9. */
  10. /*******************************
  11. TIMELINE MEDIATOR
  12. handles timeline behavior,
  13. reflects state back to view
  14. ********************************/
  15. (function(tg){
  16. var MED = {},
  17. TG_Date = tg.TG_Date,
  18. options = {},
  19. $ = jQuery,
  20. contants = {
  21. }
  22. /* In progress... */
  23. tg.TimelineCollection = Backbone.Collection.extend({
  24. model: timeglider.TG_Timeline
  25. });
  26. tg.TG_Mediator = function (wopts) {
  27. this.options = options = wopts;
  28. // quasi-private vars
  29. this._focusDate = {};
  30. this._zoomInfo = {};
  31. this._zoomLevel = 1;
  32. this.ticksReady = false;
  33. this.ticksArray = [];
  34. this.startSec = 0;
  35. this.activeTimelines = [];
  36. this.max_zoom = options.max_zoom;
  37. this.min_zoom = options.min_zoom;
  38. this.fixed_zoom = (this.max_zoom == this.min_zoom) ? true : false;
  39. this.gesturing = false;
  40. this.gestureStartZoom = 0;
  41. this.gestureStartScale = 0; // .999 etc reduced to 1 to 100
  42. this.filters = {include:"", exclude:"", legend:[]};
  43. this.eventPool = [];
  44. // this.eventPool['ev_000'] = "hello!";
  45. this.timelinePool = {};
  46. this.imagesSized = 0;
  47. this.imagesToSize = 0;
  48. this.timelineDataLoaded = false,
  49. this.foo = "bark";
  50. // this.setZoomLevel(options.initial_zoom);
  51. this.initial_timeline_id = options.initial_timeline_id || "";
  52. this.sole_timeline_id = "";
  53. if (options.max_zoom === options.min_zoom) {
  54. this.fixed_zoom = options.min_zoom;
  55. }
  56. MED = this;
  57. } // end mediator head
  58. tg.TG_Mediator.prototype = {
  59. /* PUBLIC METHODS MEDIATED BY $.widget front */
  60. gotoDate : function (fdStr) {
  61. var fd = new TG_Date(fdStr);
  62. this.setFocusDate(fd);
  63. // setting date doesn't by itself refresh: do it "manually"
  64. this.refresh();
  65. return true;
  66. },
  67. gotoDateZoom : function (fdStr, zoom) {
  68. var fd = new TG_Date(fdStr);
  69. this.setFocusDate(fd);
  70. // setting zoom _does_ refresh automatically
  71. var zlc = this.setZoomLevel(zoom);
  72. if (zlc == false) { this.refresh(); }
  73. },
  74. zoom : function (n) {
  75. var new_zoom = this.getZoomLevel() + parseInt(n);
  76. debug.log("mediator zoom:" + new_zoom);
  77. this.setZoomLevel(new_zoom);
  78. },
  79. /*
  80. * loadTimelineData
  81. * @param src {object} object OR json data to be parsed for loading
  82. * TODO: create option for XML input
  83. */
  84. loadTimelineData : function (src) {
  85. var M = this; // model ref
  86. // Allow to pass in either the url for the data or the data itself.
  87. if (src) {
  88. debug.log("HELLO??");
  89. if (typeof src === "object") {
  90. // local/pre-loaded JSON
  91. M.parseData(src);
  92. } else if (src.substr(0,1) == "#") {
  93. debug.log("IT'S A TABLE!");
  94. var tableData = [M.getTableTimelineData(src)];
  95. // debug.log(JSON.stringify(tableData));
  96. M.parseData(tableData);
  97. } else {
  98. $.getJSON(src, function (data) {
  99. M.parseData(data);
  100. }
  101. );
  102. }// end [obj vs remote]
  103. } else {
  104. // NO INITIAL DATA: That's cool, still build the timeline
  105. // focusdate has been set to today
  106. this.timelineDataLoaded = true;
  107. this.setZoomLevel(Math.floor((this.max_zoom + this.min_zoom) / 2));
  108. this.tryLoading();
  109. }
  110. },
  111. /*
  112. * getTableTimelineData
  113. * @param table_id {string} the html/DOM id of the table
  114. * @return timeline data object ready for parsing
  115. *
  116. */
  117. getTableTimelineData : function (table_id) {
  118. var tl = {},
  119. now = 0,
  120. keys = [], field, value,
  121. event_id = '',
  122. $table = $(table_id);
  123. // timeline head
  124. tl.id = table_id.substr(1);
  125. tl.title = $table.attr("title") || "untitled";
  126. tl.description = $table.attr("description") || "";
  127. tl.focus_date = $table.attr("focus_date") || TG_Date.getToday;
  128. tl.initial_zoom = $table.attr("initial_zoom") || 20;
  129. tl.events = [];
  130. $table.find('tr').each(function(i){
  131. var children = $(this).children(),
  132. row_obj;
  133. // first row -- <th> or <td>, gather the field names
  134. if ( i === 0 ) {
  135. keys = children.map(function(){
  136. // using "tg-*" map each column to the corresponding data
  137. return $(this).attr( 'class' ).replace( /^.*?\btg-(\S+)\b.*?$/, '$1' );
  138. }).get();
  139. } else {
  140. // i.e. an event
  141. row_obj = {};
  142. children.each(function(i){
  143. field = keys[i],
  144. value = $(this).text();
  145. // TODO: VALIDATE EVENT STUFF HERE
  146. row_obj[ field ] = value;
  147. });
  148. event_id = 'ev_' + now++;
  149. row_obj.id = event_id;
  150. tl.events.push(row_obj);
  151. } // end if-else i===0
  152. }); // end .each()
  153. $table.css("display", "none");
  154. return tl;
  155. },
  156. /*
  157. * parseData
  158. * @param data {object} Multiple (1+) timelines object derived from data in loadTimelineData
  159. */
  160. parseData : function (data) {
  161. var M = this; // model ref
  162. var ct = 0;
  163. var dl = data.length, ti = {}, ondeck = {};
  164. for (var i=0; i<dl;i++) {
  165. ondeck = data[i];
  166. ondeck.mediator = M;
  167. ti = new timeglider.TG_Timeline(ondeck).toJSON(); // the timeline
  168. if (ti.id.length > 0) {
  169. ct++;
  170. M.swallowTimeline(ti);
  171. }
  172. }
  173. if (ct === 0) {
  174. alert("ERROR loading data: Check JSON with jsonLint");
  175. } else {
  176. this.timelineDataLoaded = true;
  177. // might as well try!
  178. this.tryLoading();
  179. }
  180. },
  181. /*
  182. * tryLoading
  183. * Sees if all criteria for proceeding to display the loaded data
  184. * are complete: data, image sizeing and others
  185. *
  186. */
  187. tryLoading : function () {
  188. var a = (this.imagesSized == this.imagesToSize),
  189. b = (this.timelineDataLoaded == true);
  190. if (a && b) {
  191. this.setInitialTimelines();
  192. $.publish("mediator.timelineDataLoaded");
  193. }
  194. },
  195. /* Makes an indexed array of timelines */
  196. swallowTimeline : function (obj) {
  197. this.sole_timeline_id = obj.id;
  198. this.timelinePool[obj.id] = obj;
  199. $.publish("mediator.timelineListChangeSignal");
  200. },
  201. /// end of methods that need to go into (backbone) data model
  202. ///////////////////////////
  203. /*
  204. TODO: turn to $each, adding to activeTimelines:
  205. i.e. could be more than one
  206. */
  207. setInitialTimelines : function () {
  208. var me = this;
  209. var tid = this.initial_timeline_id || this.sole_timeline_id;
  210. debug.log("TID:" + tid);
  211. if (tid) {
  212. setTimeout(function () {
  213. MED.toggleTimeline(tid);
  214. }, 1000);
  215. }
  216. },
  217. refresh : function () {
  218. this.startSec = this._focusDate.sec;
  219. $.publish("mediator.refreshSignal");
  220. },
  221. // !!!TODO ---- get these back to normal setTicksReady, etc.
  222. setTicksReady : function (bool) {
  223. this.ticksReady = bool;
  224. if (bool === true) {
  225. $.publish("mediator.ticksReadySignal");
  226. }
  227. },
  228. getFocusDate : function () {
  229. return this._focusDate;
  230. },
  231. /*
  232. * setFocusDate
  233. * @param fd [TG_Date instance]
  234. *
  235. */
  236. setFocusDate : function (fd) {
  237. // !TODO :: VALIDATE FOCUS DATE
  238. if (fd != this._focusDate) {
  239. this._focusDate = fd;
  240. debug.trace("setting fd:" + fd.dateStr, "note");
  241. }
  242. },
  243. /*
  244. * getZoomLevel
  245. * @return {Number} zoom level number from 1 to 100
  246. *
  247. *
  248. *
  249. */
  250. getZoomLevel : function () {
  251. return parseInt(this._zoomLevel);
  252. },
  253. /*
  254. * setZoomLevel
  255. * This in turn sets other zoomInfo attributes : width, label, tickWidth
  256. * Other zoom info comes from the zoomTree array
  257. * @param z ==> integer from 1-100
  258. *
  259. */
  260. setZoomLevel : function (z) {
  261. if (z <= this.max_zoom && z >= this.min_zoom) {
  262. // focusdate has to come first for combined zoom+focusdate switch
  263. this.startSec = this._focusDate.sec;
  264. if (z != this._zoomLevel) {
  265. this._zoomLevel = z;
  266. this._zoomInfo = timeglider.zoomTree[z];
  267. $.publish("mediator.zoomLevelChange");
  268. return true
  269. } else {
  270. return false;
  271. }
  272. // end min/max check
  273. } else { return false; }
  274. },
  275. /*
  276. * getZoomInfo
  277. * @return obj {Object} with
  278. * zoomLevel (Number), label (String), tickWidth (Number), unit (String)
  279. *
  280. */
  281. getZoomInfo : function () {
  282. return this._zoomInfo;
  283. },
  284. /*
  285. * setFilters
  286. * @param obj {Object} containing:
  287. * origin ("clude", "legend"), include (Str), exclude (Str), legend (Obj)
  288. *
  289. */
  290. setFilters : function (obj) {
  291. switch (obj.origin) {
  292. case "clude":
  293. this.filters.include = obj.include;
  294. this.filters.exclude = obj.exclude;
  295. break;
  296. case "legend":
  297. var icon = obj.icon;
  298. if (icon == "all") {
  299. this.filters.legend = [];
  300. $.publish("mediator.legendAll");
  301. } else {
  302. if ($.inArray(icon, this.filters.legend) == -1) {
  303. this.filters.legend.push(icon);
  304. } else {
  305. // remove it
  306. var fol = this.filters.legend;
  307. var fr = [];
  308. fr = $.grep(fol, function (a) { return a != icon; });
  309. this.filters.legend = fr;
  310. }
  311. } // end if/else for "clear"
  312. break;
  313. } // end switch
  314. $.publish("mediator.filtersChange");
  315. this.refresh();
  316. },
  317. getTicksOffset : function () {
  318. return this._ticksOffset;
  319. },
  320. setTicksOffset : function (newOffset) {
  321. // This triggers changing the focus date
  322. // main listener hub for date focus and tick-appending
  323. this._ticksOffset = newOffset;
  324. /* In other words, ticks are being dragged! */
  325. $.publish( "mediator.ticksOffsetChange" );
  326. },
  327. /*
  328. * getTickBySerial
  329. * @param serial {Number} serial date unit number (rata die, monthnum, year, etc)
  330. *
  331. * @return {Object} info about _existing_ displayed tick
  332. *
  333. */
  334. getTickBySerial : function (serial) {
  335. var ta = this.ticksArray,
  336. tal = ta.length;
  337. for (var t=0; t<tal; t++) {
  338. var tick = ta[t];
  339. if (tick.serial == serial) { return tick; }
  340. }
  341. return false;
  342. },
  343. /*
  344. * addToTicksArray
  345. * @param obj {Object}
  346. * serial: #initial tick
  347. * type:init|l|r
  348. * unit:ye | mo | da | etc
  349. * width: #px
  350. * left: #px
  351. * @param focusDate {TG_Date}
  352. * used for initial tick; others set off init
  353. */
  354. addToTicksArray : function (obj, focusDate) {
  355. if (obj.type == "init") {
  356. // CENTER
  357. obj.serial = TG_Date.getTimeUnitSerial(focusDate, obj.unit);
  358. this.ticksArray = [obj];
  359. } else if (obj.type == "l") {
  360. // LEFT
  361. obj.serial = this.ticksArray[0].serial - 1;
  362. this.ticksArray.unshift(obj);
  363. } else {
  364. // RIGHT SIDE
  365. obj.serial = this.ticksArray[this.ticksArray.length -1].serial + 1;
  366. this.ticksArray.push(obj);
  367. }
  368. // this.ticksArrayChange.broadcast();
  369. $.publish( "mediator.ticksArrayChange" );
  370. return obj.serial;
  371. },
  372. toggleTimeline : function (id) {
  373. var lt = this.timelinePool[id];
  374. var ia = $.inArray(id, this.activeTimelines);
  375. if (ia == -1) {
  376. // The timeline is
  377. // not active ---- bring it on
  378. this.activeTimelines.push(id);
  379. // setting FD does NOT refresh
  380. // timeline focus_date is ISO-8601 basic
  381. // ==== new TG_Date()
  382. var tl_fd = new TG_Date(lt.focus_date);
  383. this.setFocusDate(tl_fd);
  384. // resetting zoomLevel DOES refresh
  385. this.setZoomLevel(lt.initial_zoom);
  386. } else {
  387. // it's active, remove it
  388. this.activeTimelines.splice(ia,1);
  389. }
  390. this.refresh();
  391. // this will change the menu list/appearance
  392. $.publish( "mediator.activeTimelinesChange" );
  393. },
  394. /*
  395. * reportImageSize
  396. * @param img {Object} has "id" of event, "src", "width" and "height" at least
  397. *
  398. * This information is reported from TG_Timeline as data is loading. Since image
  399. * size gathering sidetracks from data loading, there's a
  400. *
  401. */
  402. reportImageSize : function (img) {
  403. var ev = MED.eventPool["ev_" + img.id];
  404. if (!img.error) {
  405. ev.image.width = img.width;
  406. ev.image.height = img.height;
  407. } else {
  408. ev.image = {}
  409. debug.log("WHOOPS: MISSING IMAGE: " + img.src);
  410. }
  411. this.imagesSized++;
  412. if (this.imagesSized == this.imagesToSize) {
  413. // if there are images, this would usually be
  414. // the last step before proceeding
  415. this.tryLoading();
  416. }
  417. }
  418. ///// end model prototype object
  419. };
  420. tg.getLowHigh = function (arr) {
  421. var i, n,
  422. high = parseFloat(arr[0]),
  423. low = high;
  424. for (i=0; i<arr.length; i++) {
  425. n = parseFloat(arr[i]);
  426. if (n<low) low = n;
  427. if (n>high) high = n;
  428. }
  429. return {"high":high, "low":low}
  430. };
  431. tg.validateOptions = function (widget_settings) {
  432. this.optionsMaster = { initial_focus:{type:"date"},
  433. editor:{type:"string"},
  434. backgroundColor:{type:"color"},
  435. backgroundImage:{type:"color"},
  436. min_zoom:{type:"number", min:1, max:100},
  437. max_zoom:{type:"number", min:1, max:100},
  438. initial_zoom:{type:"number", min:1, max:100},
  439. show_centerline:{type:"boolean"},
  440. display_zoom_level:{type:"boolean"},
  441. data_source:{type:"url"},
  442. basic_fontsize:{type:"number", min:9, max:100},
  443. mouse_wheel:{type:"string", possible:["zoom","pan"]},
  444. initial_timeline_id:{type:"string"},
  445. icon_folder:{type:"string"},
  446. show_footer:{type:"boolean"},
  447. display_zoom_level:{type:"boolean"}
  448. }
  449. // msg: will be return value: validates when empty
  450. // change lb to <br> if the error is returned in HTML (vs alert())
  451. var me = this, msg = "", lb = "\n";
  452. $.each(widget_settings, function(key, value) {
  453. if (me.optionsMaster[key]) {
  454. switch (me.optionsMaster[key].type) {
  455. case "string":
  456. if (typeof value != "string") { msg += (key + " needs to be a string." + lb); }
  457. if (me.optionsMaster[key].possible) {
  458. if ($.inArray(value, me.optionsMaster[key].possible) == -1) {
  459. msg += (key + " must be: " + me.optionsMaster[key].possible.join(" or "));
  460. }
  461. }
  462. break;
  463. case "number":
  464. if (typeof value != "number") { msg += (value + " needs to be a number." + lb); }
  465. if (me.optionsMaster[key].min) {
  466. if (value < me.optionsMaster[key].min) {
  467. msg += (key + " must be greater than or equal to " + me.optionsMaster[key].min + lb);
  468. }
  469. }
  470. if (me.optionsMaster[key].max) {
  471. if (value > me.optionsMaster[key].max) {
  472. msg += (key + " must be less than or equal to " + me.optionsMaster[key].max + lb);
  473. }
  474. }
  475. break;
  476. case "date":
  477. // TODO validate a date string using TG_Date...
  478. break;
  479. case "boolean":
  480. if (typeof value != "boolean") msg += (value + " needs to be a number." + lb);
  481. break;
  482. case "url":
  483. // TODO test for pattern for url....
  484. break;
  485. case "color":
  486. /// TODO test for pattern for color, including "red", "orange", etc
  487. break;
  488. }
  489. }
  490. }); // end each
  491. return msg;
  492. };
  493. })(timeglider);