1/*================================================== 2 * Default Event Source 3 *================================================== 4 */ 5Timeline.DefaultEventSource = function(eventIndex) { 6 this._events = (eventIndex instanceof Object) ? eventIndex : new SimileAjax.EventIndex(); 7 this._listeners = []; 8}; 9 10Timeline.DefaultEventSource.prototype.addListener = function(listener) { 11 this._listeners.push(listener); 12}; 13 14Timeline.DefaultEventSource.prototype.removeListener = function(listener) { 15 for (var i = 0; i < this._listeners.length; i++) { 16 if (this._listeners[i] == listener) { 17 this._listeners.splice(i, 1); 18 break; 19 } 20 } 21}; 22 23Timeline.DefaultEventSource.prototype.loadXML = function(xml, url) { 24 var base = this._getBaseURL(url); 25 26 var wikiURL = xml.documentElement.getAttribute("wiki-url"); 27 var wikiSection = xml.documentElement.getAttribute("wiki-section"); 28 29 var dateTimeFormat = xml.documentElement.getAttribute("date-time-format"); 30 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); 31 32 var node = xml.documentElement.firstChild; 33 var added = false; 34 while (node != null) { 35 if (node.nodeType == 1) { 36 var description = ""; 37 if (node.firstChild != null && node.firstChild.nodeType == 3) { 38// description = node.firstChild.nodeValue; 39 description = this.concatChildValues(node) } 40 // instant event: default is true. Or use values from isDuration or durationEvent 41 var instant = (node.getAttribute("isDuration") === null && 42 node.getAttribute("durationEvent") === null) || 43 node.getAttribute("isDuration") == "false" || 44 node.getAttribute("durationEvent") == "false"; 45 46 var evt = new Timeline.DefaultEventSource.Event( { 47 id: node.getAttribute("id"), 48 start: parseDateTimeFunction(node.getAttribute("start")), 49 end: parseDateTimeFunction(node.getAttribute("end")), 50 latestStart: parseDateTimeFunction(node.getAttribute("latestStart")), 51 earliestEnd: parseDateTimeFunction(node.getAttribute("earliestEnd")), 52 instant: instant, 53 text: node.getAttribute("title"), 54 description: description, 55 image: this._resolveRelativeURL(node.getAttribute("image"), base), 56 link: this._resolveRelativeURL(node.getAttribute("link") , base), 57 icon: this._resolveRelativeURL(node.getAttribute("icon") , base), 58 color: node.getAttribute("color"), 59 textColor: node.getAttribute("textColor"), 60 hoverText: node.getAttribute("hoverText"), 61 classname: node.getAttribute("classname"), 62 tapeImage: node.getAttribute("tapeImage"), 63 tapeRepeat: node.getAttribute("tapeRepeat"), 64 caption: node.getAttribute("caption"), 65 eventID: node.getAttribute("eventID"), 66 trackNum: node.getAttribute("trackNum") 67 }); 68 69 evt._node = node; 70 evt.getProperty = function(name) { 71 return this._node.getAttribute(name); 72 }; 73 evt.setWikiInfo(wikiURL, wikiSection); 74 75 this._events.add(evt); 76 77 added = true; 78 } 79 node = node.nextSibling; 80 } 81 82 if (added) { 83 this._fire("onAddMany", []); 84 } 85}; 86 87 88Timeline.DefaultEventSource.prototype.loadJSON = function(data, url) { 89 var base = this._getBaseURL(url); 90 var added = false; 91 if (data && data.events){ 92 var wikiURL = ("wikiURL" in data) ? data.wikiURL : null; 93 var wikiSection = ("wikiSection" in data) ? data.wikiSection : null; 94 95 var dateTimeFormat = ("dateTimeFormat" in data) ? data.dateTimeFormat : null; 96 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); 97 98 for (var i=0; i < data.events.length; i++){ 99 var evnt = data.events[i]; 100 101 // New feature: attribute synonyms. The following attribute names are interchangable. 102 // The shorter names enable smaller load files. 103 // eid -- eventID 104 // s -- start 105 // e -- end 106 // ls -- latestStart 107 // ee -- earliestEnd 108 // d -- description 109 // de -- durationEvent 110 // t -- title, 111 // c -- classname 112 113 // Fixing issue 33: 114 // instant event: default (for JSON only) is false. Or use values from isDuration or durationEvent 115 // isDuration was negated (see issue 33, so keep that interpretation 116 var instant = evnt.isDuration || 117 (('durationEvent' in evnt) && !evnt.durationEvent) || 118 (('de' in evnt) && !evnt.de); 119 var evt = new Timeline.DefaultEventSource.Event({ 120 id: ("id" in evnt) ? evnt.id : undefined, 121 start: parseDateTimeFunction(evnt.start || evnt.s), 122 end: parseDateTimeFunction(evnt.end || evnt.e), 123 latestStart: parseDateTimeFunction(evnt.latestStart || evnt.ls), 124 earliestEnd: parseDateTimeFunction(evnt.earliestEnd || evnt.ee), 125 instant: instant, 126 text: evnt.title || evnt.t, 127 description: evnt.description || evnt.d, 128 image: this._resolveRelativeURL(evnt.image, base), 129 link: this._resolveRelativeURL(evnt.link , base), 130 icon: this._resolveRelativeURL(evnt.icon , base), 131 color: evnt.color, 132 textColor: evnt.textColor, 133 hoverText: evnt.hoverText, 134 classname: evnt.classname || evnt.c, 135 tapeImage: evnt.tapeImage, 136 tapeRepeat: evnt.tapeRepeat, 137 caption: evnt.caption, 138 eventID: evnt.eventID || evnt.eid, 139 trackNum: evnt.trackNum 140 }); 141 evt._obj = evnt; 142 evt.getProperty = function(name) { 143 return this._obj[name]; 144 }; 145 evt.setWikiInfo(wikiURL, wikiSection); 146 147 this._events.add(evt); 148 added = true; 149 } 150 } 151 152 if (added) { 153 this._fire("onAddMany", []); 154 } 155}; 156 157/* 158 * Contributed by Morten Frederiksen, http://www.wasab.dk/morten/ 159 */ 160Timeline.DefaultEventSource.prototype.loadSPARQL = function(xml, url) { 161 var base = this._getBaseURL(url); 162 163 var dateTimeFormat = 'iso8601'; 164 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); 165 166 if (xml == null) { 167 return; 168 } 169 170 /* 171 * Find <results> tag 172 */ 173 var node = xml.documentElement.firstChild; 174 while (node != null && (node.nodeType != 1 || node.nodeName != 'results')) { 175 node = node.nextSibling; 176 } 177 178 var wikiURL = null; 179 var wikiSection = null; 180 if (node != null) { 181 wikiURL = node.getAttribute("wiki-url"); 182 wikiSection = node.getAttribute("wiki-section"); 183 184 node = node.firstChild; 185 } 186 187 var added = false; 188 while (node != null) { 189 if (node.nodeType == 1) { 190 var bindings = { }; 191 var binding = node.firstChild; 192 while (binding != null) { 193 if (binding.nodeType == 1 && 194 binding.firstChild != null && 195 binding.firstChild.nodeType == 1 && 196 binding.firstChild.firstChild != null && 197 binding.firstChild.firstChild.nodeType == 3) { 198 bindings[binding.getAttribute('name')] = binding.firstChild.firstChild.nodeValue; 199 } 200 binding = binding.nextSibling; 201 } 202 203 if (bindings["start"] == null && bindings["date"] != null) { 204 bindings["start"] = bindings["date"]; 205 } 206 207 // instant event: default is true. Or use values from isDuration or durationEvent 208 var instant = (bindings["isDuration"] === null && 209 bindings["durationEvent"] === null) || 210 bindings["isDuration"] == "false" || 211 bindings["durationEvent"] == "false"; 212 213 var evt = new Timeline.DefaultEventSource.Event({ 214 id: bindings["id"], 215 start: parseDateTimeFunction(bindings["start"]), 216 end: parseDateTimeFunction(bindings["end"]), 217 latestStart: parseDateTimeFunction(bindings["latestStart"]), 218 earliestEnd: parseDateTimeFunction(bindings["earliestEnd"]), 219 instant: instant, // instant 220 text: bindings["title"], // text 221 description: bindings["description"], 222 image: this._resolveRelativeURL(bindings["image"], base), 223 link: this._resolveRelativeURL(bindings["link"] , base), 224 icon: this._resolveRelativeURL(bindings["icon"] , base), 225 color: bindings["color"], 226 textColor: bindings["textColor"], 227 hoverText: bindings["hoverText"], 228 caption: bindings["caption"], 229 classname: bindings["classname"], 230 tapeImage: bindings["tapeImage"], 231 tapeRepeat: bindings["tapeRepeat"], 232 eventID: bindings["eventID"], 233 trackNum: bindings["trackNum"] 234 }); 235 evt._bindings = bindings; 236 evt.getProperty = function(name) { 237 return this._bindings[name]; 238 }; 239 evt.setWikiInfo(wikiURL, wikiSection); 240 241 this._events.add(evt); 242 added = true; 243 } 244 node = node.nextSibling; 245 } 246 247 if (added) { 248 this._fire("onAddMany", []); 249 } 250}; 251 252Timeline.DefaultEventSource.prototype.add = function(evt) { 253 this._events.add(evt); 254 this._fire("onAddOne", [evt]); 255}; 256 257Timeline.DefaultEventSource.prototype.addMany = function(events) { 258 for (var i = 0; i < events.length; i++) { 259 this._events.add(events[i]); 260 } 261 this._fire("onAddMany", []); 262}; 263 264Timeline.DefaultEventSource.prototype.clear = function() { 265 this._events.removeAll(); 266 this._fire("onClear", []); 267}; 268 269Timeline.DefaultEventSource.prototype.getEvent = function(id) { 270 return this._events.getEvent(id); 271}; 272 273Timeline.DefaultEventSource.prototype.getEventIterator = function(startDate, endDate) { 274 return this._events.getIterator(startDate, endDate); 275}; 276 277Timeline.DefaultEventSource.prototype.getEventReverseIterator = function(startDate, endDate) { 278 return this._events.getReverseIterator(startDate, endDate); 279}; 280 281Timeline.DefaultEventSource.prototype.getAllEventIterator = function() { 282 return this._events.getAllIterator(); 283}; 284 285Timeline.DefaultEventSource.prototype.getCount = function() { 286 return this._events.getCount(); 287}; 288 289Timeline.DefaultEventSource.prototype.getEarliestDate = function() { 290 return this._events.getEarliestDate(); 291}; 292 293Timeline.DefaultEventSource.prototype.getLatestDate = function() { 294 return this._events.getLatestDate(); 295}; 296 297Timeline.DefaultEventSource.prototype._fire = function(handlerName, args) { 298 for (var i = 0; i < this._listeners.length; i++) { 299 var listener = this._listeners[i]; 300 if (handlerName in listener) { 301 try { 302 listener[handlerName].apply(listener, args); 303 } catch (e) { 304 SimileAjax.Debug.exception(e); 305 } 306 } 307 } 308}; 309 310Timeline.DefaultEventSource.prototype._getBaseURL = function(url) { 311 if (url.indexOf("://") < 0) { 312 var url2 = this._getBaseURL(document.location.href); 313 if (url.substr(0,1) == "/") { 314 url = url2.substr(0, url2.indexOf("/", url2.indexOf("://") + 3)) + url; 315 } else { 316 url = url2 + url; 317 } 318 } 319 320 var i = url.lastIndexOf("/"); 321 if (i < 0) { 322 return ""; 323 } else { 324 return url.substr(0, i+1); 325 } 326}; 327 328Timeline.DefaultEventSource.prototype._resolveRelativeURL = function(url, base) { 329 if (url == null || url == "") { 330 return url; 331 } else if (url.indexOf("://") > 0) { 332 return url; 333 } else if (url.substr(0,1) == "/") { 334 return base.substr(0, base.indexOf("/", base.indexOf("://") + 3)) + url; 335 } else { 336 return base + url; 337 } 338}; 339 340 341Timeline.DefaultEventSource.Event = function(args) { 342 // 343 // Attention developers! 344 // If you add a new event attribute, please be sure to add it to 345 // all three load functions: loadXML, loadSPARCL, loadJSON. 346 // Thanks! 347 // 348 // args is a hash/object. It supports the following keys. Most are optional 349 // id -- an internal id. Really shouldn't be used by events. 350 // Timeline library clients should use eventID 351 // eventID -- For use by library client when writing custom painters or 352 // custom fillInfoBubble 353 // start 354 // end 355 // latestStart 356 // earliestEnd 357 // instant -- boolean. Controls precise/non-precise logic & duration/instant issues 358 // text -- event source attribute 'title' -- used as the label on Timelines and in bubbles. 359 // description -- used in bubbles 360 // image -- used in bubbles 361 // link -- used in bubbles 362 // icon -- on the Timeline 363 // color -- Timeline label and tape color 364 // textColor -- Timeline label color, overrides color attribute 365 // hoverText -- deprecated, here for backwards compatibility. 366 // Superceeded by caption 367 // caption -- tooltip-like caption on the Timeline. Uses HTML title attribute 368 // classname -- used to set classname in Timeline. Enables better CSS selector rules 369 // tapeImage -- background image of the duration event's tape div on the Timeline 370 // tapeRepeat -- repeat attribute for tapeImage. {repeat | repeat-x | repeat-y } 371 372 function cleanArg(arg) { 373 // clean up an arg 374 return (args[arg] != null && args[arg] != "") ? args[arg] : null; 375 } 376 377 var id = args.id ? args.id.trim() : ""; 378 this._id = id.length > 0 ? id : Timeline.EventUtils.getNewEventID(); 379 380 this._instant = args.instant || (args.end == null); 381 382 this._start = args.start; 383 this._end = (args.end != null) ? args.end : args.start; 384 385 this._latestStart = (args.latestStart != null) ? 386 args.latestStart : (args.instant ? this._end : this._start); 387 this._earliestEnd = (args.earliestEnd != null) ? args.earliestEnd : this._end; 388 389 // check sanity of dates since incorrect dates will later cause calculation errors 390 // when painting 391 var err=[]; 392 if (this._start > this._latestStart) { 393 this._latestStart = this._start; 394 err.push("start is > latestStart");} 395 if (this._start > this._earliestEnd) { 396 this._earliestEnd = this._latestStart; 397 err.push("start is > earliestEnd");} 398 if (this._start > this._end) { 399 this._end = this._earliestEnd; 400 err.push("start is > end");} 401 if (this._latestStart > this._earliestEnd) { 402 this._earliestEnd = this._latestStart; 403 err.push("latestStart is > earliestEnd");} 404 if (this._latestStart > this._end) { 405 this._end = this._earliestEnd; 406 err.push("latestStart is > end");} 407 if (this._earliestEnd > this._end) { 408 this._end = this._earliestEnd; 409 err.push("earliestEnd is > end");} 410 411 this._eventID = cleanArg('eventID'); 412 this._text = (args.text != null) ? SimileAjax.HTML.deEntify(args.text) : ""; // Change blank titles to "" 413 if (err.length > 0) { 414 this._text += " PROBLEM: " + err.join(", "); 415 } 416 417 this._description = SimileAjax.HTML.deEntify(args.description); 418 this._image = cleanArg('image'); 419 this._link = cleanArg('link'); 420 this._title = cleanArg('hoverText'); 421 this._title = cleanArg('caption'); 422 423 this._icon = cleanArg('icon'); 424 this._color = cleanArg('color'); 425 this._textColor = cleanArg('textColor'); 426 this._classname = cleanArg('classname'); 427 this._tapeImage = cleanArg('tapeImage'); 428 this._tapeRepeat = cleanArg('tapeRepeat'); 429 this._trackNum = cleanArg('trackNum'); 430 if (this._trackNum != null) { 431 this._trackNum = parseInt(this._trackNum); 432 } 433 434 this._wikiURL = null; 435 this._wikiSection = null; 436}; 437 438Timeline.DefaultEventSource.Event.prototype = { 439 getID: function() { return this._id; }, 440 441 isInstant: function() { return this._instant; }, 442 isImprecise: function() { return this._start != this._latestStart || this._end != this._earliestEnd; }, 443 444 getStart: function() { return this._start; }, 445 getEnd: function() { return this._end; }, 446 getLatestStart: function() { return this._latestStart; }, 447 getEarliestEnd: function() { return this._earliestEnd; }, 448 449 getEventID: function() { return this._eventID; }, 450 getText: function() { return this._text; }, // title 451 getDescription: function() { return this._description; }, 452 getImage: function() { return this._image; }, 453 getLink: function() { return this._link; }, 454 455 getIcon: function() { return this._icon; }, 456 getColor: function() { return this._color; }, 457 getTextColor: function() { return this._textColor; }, 458 getClassName: function() { return this._classname; }, 459 getTapeImage: function() { return this._tapeImage; }, 460 getTapeRepeat: function() { return this._tapeRepeat; }, 461 getTrackNum: function() { return this._trackNum; }, 462 463 getProperty: function(name) { return null; }, 464 465 getWikiURL: function() { return this._wikiURL; }, 466 getWikiSection: function() { return this._wikiSection; }, 467 setWikiInfo: function(wikiURL, wikiSection) { 468 this._wikiURL = wikiURL; 469 this._wikiSection = wikiSection; 470 }, 471 472 fillDescription: function(elmt) { 473 if (this._description) { 474 elmt.innerHTML = this._description; 475 } 476 }, 477 fillWikiInfo: function(elmt) { 478 // Many bubbles will not support a wiki link. 479 // 480 // Strategy: assume no wiki link. If we do have 481 // enough parameters for one, then create it. 482 elmt.style.display = "none"; // default 483 484 if (this._wikiURL == null || this._wikiSection == null) { 485 return; // EARLY RETURN 486 } 487 488 // create the wikiID from the property or from the event text (the title) 489 var wikiID = this.getProperty("wikiID"); 490 if (wikiID == null || wikiID.length == 0) { 491 wikiID = this.getText(); // use the title as the backup wiki id 492 } 493 494 if (wikiID == null || wikiID.length == 0) { 495 return; // No wikiID. Thus EARLY RETURN 496 } 497 498 // ready to go... 499 elmt.style.display = "inline"; 500 wikiID = wikiID.replace(/\s/g, "_"); 501 var url = this._wikiURL + this._wikiSection.replace(/\s/g, "_") + "/" + wikiID; 502 var a = document.createElement("a"); 503 a.href = url; 504 a.target = "new"; 505 a.innerHTML = Timeline.strings[Timeline.clientLocale].wikiLinkLabel; 506 507 elmt.appendChild(document.createTextNode("[")); 508 elmt.appendChild(a); 509 elmt.appendChild(document.createTextNode("]")); 510 }, 511 512 fillTime: function(elmt, labeller) { 513 if (this._instant) { 514 if (this.isImprecise()) { 515 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); 516 elmt.appendChild(elmt.ownerDocument.createElement("br")); 517 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end))); 518 } else { 519 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); 520 } 521 } else { 522 if (this.isImprecise()) { 523 elmt.appendChild(elmt.ownerDocument.createTextNode( 524 labeller.labelPrecise(this._start) + " ~ " + labeller.labelPrecise(this._latestStart))); 525 elmt.appendChild(elmt.ownerDocument.createElement("br")); 526 elmt.appendChild(elmt.ownerDocument.createTextNode( 527 labeller.labelPrecise(this._earliestEnd) + " ~ " + labeller.labelPrecise(this._end))); 528 } else { 529 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); 530 elmt.appendChild(elmt.ownerDocument.createElement("br")); 531 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end))); 532 } 533 } 534 }, 535 536 fillInfoBubble: function(elmt, theme, labeller) { 537 var doc = elmt.ownerDocument; 538 539 var title = this.getText(); 540 var link = this.getLink(); 541 var image = this.getImage(); 542 543 if (image != null) { 544 var img = doc.createElement("img"); 545 img.src = image; 546 547 theme.event.bubble.imageStyler(img); 548 elmt.appendChild(img); 549 } 550 551 var divTitle = doc.createElement("div"); 552 var textTitle = doc.createTextNode(title); 553 if (link != null) { 554 var a = doc.createElement("a"); 555 a.href = link; 556 a.appendChild(textTitle); 557 divTitle.appendChild(a); 558 } else { 559 divTitle.appendChild(textTitle); 560 } 561 theme.event.bubble.titleStyler(divTitle); 562 elmt.appendChild(divTitle); 563 564 var divBody = doc.createElement("div"); 565 this.fillDescription(divBody); 566 theme.event.bubble.bodyStyler(divBody); 567 elmt.appendChild(divBody); 568 569 var divTime = doc.createElement("div"); 570 this.fillTime(divTime, labeller); 571 theme.event.bubble.timeStyler(divTime); 572 elmt.appendChild(divTime); 573 574 var divWiki = doc.createElement("div"); 575 this.fillWikiInfo(divWiki); 576 theme.event.bubble.wikiStyler(divWiki); 577 elmt.appendChild(divWiki); 578 } 579}; 580 581 582