1 /* |
|
2 * |
|
3 * Copyright 2010 Institut de recherche et d'innovation |
|
4 * contributor(s) : Samuel Huron |
|
5 * |
|
6 * contact@iri.centrepompidou.fr |
|
7 * http://www.iri.centrepompidou.fr |
|
8 * |
|
9 * This software is a computer program whose purpose is to show and add annotations on a video . |
|
10 * This software is governed by the CeCILL-C license under French law and |
|
11 * abiding by the rules of distribution of free software. You can use, |
|
12 * modify and/ or redistribute the software under the terms of the CeCILL-C |
|
13 * license as circulated by CEA, CNRS and INRIA at the following URL |
|
14 * "http://www.cecill.info". |
|
15 * |
|
16 * The fact that you are presently reading this means that you have had |
|
17 * knowledge of the CeCILL-C license and that you accept its terms. |
|
18 */ |
|
19 /*! LAB.js (LABjs :: Loading And Blocking JavaScript) |
|
20 v2.0.3 (c) Kyle Simpson |
|
21 MIT License |
|
22 */ |
|
23 |
|
24 (function(global){ |
|
25 var _$LAB = global.$LAB, |
|
26 |
|
27 // constants for the valid keys of the options object |
|
28 _UseLocalXHR = "UseLocalXHR", |
|
29 _AlwaysPreserveOrder = "AlwaysPreserveOrder", |
|
30 _AllowDuplicates = "AllowDuplicates", |
|
31 _CacheBust = "CacheBust", |
|
32 /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ |
|
33 _BasePath = "BasePath", |
|
34 |
|
35 // stateless variables used across all $LAB instances |
|
36 root_page = /^[^?#]*\//.exec(location.href)[0], |
|
37 root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], |
|
38 append_to = document.head || document.getElementsByTagName("head"), |
|
39 |
|
40 // inferences... ick, but still necessary |
|
41 opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), |
|
42 |
|
43 /*!START_DEBUG*/ |
|
44 // console.log() and console.error() wrappers |
|
45 log_msg = function(){}, |
|
46 log_error = log_msg, |
|
47 /*!END_DEBUG*/ |
|
48 |
|
49 // feature sniffs (yay!) |
|
50 test_script_elem = document.createElement("script"), |
|
51 explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 |
|
52 real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? |
|
53 script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
54 |
|
55 // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) |
|
56 xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko |
|
57 ; |
|
58 |
|
59 /*!START_DEBUG*/ |
|
60 // define console wrapper functions if applicable |
|
61 if (global.console && global.console.log) { |
|
62 if (!global.console.error) global.console.error = global.console.log; |
|
63 log_msg = function(msg) { global.console.log(msg); }; |
|
64 log_error = function(msg,err) { global.console.error(msg,err); }; |
|
65 } |
|
66 /*!END_DEBUG*/ |
|
67 |
|
68 // test for function |
|
69 function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; } |
|
70 |
|
71 // test for array |
|
72 function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } |
|
73 |
|
74 // make script URL absolute/canonical |
|
75 function canonical_uri(src,base_path) { |
|
76 var absolute_regex = /^\w+\:\/\//; |
|
77 |
|
78 // is `src` is protocol-relative (begins with // or ///), prepend protocol |
|
79 if (/^\/\/\/?/.test(src)) { |
|
80 src = location.protocol + src; |
|
81 } |
|
82 // is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /) |
|
83 else if (!absolute_regex.test(src) && src.charAt(0) != "/") { |
|
84 // prepend `base_path`, if any |
|
85 src = (base_path || "") + src; |
|
86 } |
|
87 // make sure to return `src` as absolute |
|
88 return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src); |
|
89 } |
|
90 |
|
91 // merge `source` into `target` |
|
92 function merge_objs(source,target) { |
|
93 for (var k in source) { if (source.hasOwnProperty(k)) { |
|
94 target[k] = source[k]; // TODO: does this need to be recursive for our purposes? |
|
95 }} |
|
96 return target; |
|
97 } |
|
98 |
|
99 // does the chain group have any ready-to-execute scripts? |
|
100 function check_chain_group_scripts_ready(chain_group) { |
|
101 var any_scripts_ready = false; |
|
102 for (var i=0; i<chain_group.scripts.length; i++) { |
|
103 if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) { |
|
104 any_scripts_ready = true; |
|
105 chain_group.scripts[i].exec_trigger(); |
|
106 chain_group.scripts[i].exec_trigger = null; |
|
107 } |
|
108 } |
|
109 return any_scripts_ready; |
|
110 } |
|
111 |
|
112 // creates a script load listener |
|
113 function create_script_load_listener(elem,registry_item,flag,onload) { |
|
114 elem.onload = elem.onreadystatechange = function() { |
|
115 if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return; |
|
116 elem.onload = elem.onreadystatechange = null; |
|
117 onload(); |
|
118 }; |
|
119 } |
|
120 |
|
121 // script executed handler |
|
122 function script_executed(registry_item) { |
|
123 registry_item.ready = registry_item.finished = true; |
|
124 for (var i=0; i<registry_item.finished_listeners.length; i++) { |
|
125 registry_item.finished_listeners[i](); |
|
126 } |
|
127 registry_item.ready_listeners = []; |
|
128 registry_item.finished_listeners = []; |
|
129 } |
|
130 |
|
131 // make the request for a scriptha |
|
132 function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) { |
|
133 // setTimeout() "yielding" prevents some weird race/crash conditions in older browsers |
|
134 setTimeout(function(){ |
|
135 var script, src = script_obj.real_src, xhr; |
|
136 |
|
137 // don't proceed until `append_to` is ready to append to |
|
138 if ("item" in append_to) { // check if `append_to` ref is still a live node list |
|
139 if (!append_to[0]) { // `append_to` node not yet ready |
|
140 // try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()` |
|
141 setTimeout(arguments.callee,25); |
|
142 return; |
|
143 } |
|
144 // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists |
|
145 append_to = append_to[0]; |
|
146 } |
|
147 script = document.createElement("script"); |
|
148 if (script_obj.type) script.type = script_obj.type; |
|
149 if (script_obj.charset) script.charset = script_obj.charset; |
|
150 |
|
151 // should preloading be used for this script? |
|
152 if (preload_this_script) { |
|
153 // real script preloading? |
|
154 if (real_preloading) { |
|
155 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/ |
|
156 registry_item.elem = script; |
|
157 if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal) |
|
158 script.preload = true; |
|
159 script.onpreload = onload; |
|
160 } |
|
161 else { |
|
162 script.onreadystatechange = function(){ |
|
163 if (script.readyState == "loaded") onload(); |
|
164 }; |
|
165 } |
|
166 script.src = src; |
|
167 // NOTE: no append to DOM yet, appending will happen when ready to execute |
|
168 } |
|
169 // same-domain and XHR allowed? use XHR preloading |
|
170 else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) { |
|
171 xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7 |
|
172 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/ |
|
173 xhr.onreadystatechange = function() { |
|
174 if (xhr.readyState == 4) { |
|
175 xhr.onreadystatechange = function(){}; // fix a memory leak in IE |
|
176 registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ |
|
177 onload(); |
|
178 } |
|
179 }; |
|
180 xhr.open("GET",src); |
|
181 xhr.send(); |
|
182 } |
|
183 // as a last resort, use cache-preloading |
|
184 else { |
|
185 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/ |
|
186 script.type = "text/cache-script"; |
|
187 create_script_load_listener(script,registry_item,"ready",function() { |
|
188 append_to.removeChild(script); |
|
189 onload(); |
|
190 }); |
|
191 script.src = src; |
|
192 append_to.insertBefore(script,append_to.firstChild); |
|
193 } |
|
194 } |
|
195 // use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
196 else if (script_ordered_async) { |
|
197 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/ |
|
198 script.async = false; |
|
199 create_script_load_listener(script,registry_item,"finished",onload); |
|
200 script.src = src; |
|
201 append_to.insertBefore(script,append_to.firstChild); |
|
202 } |
|
203 // otherwise, just a normal script element |
|
204 else { |
|
205 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/ |
|
206 create_script_load_listener(script,registry_item,"finished",onload); |
|
207 script.src = src; |
|
208 append_to.insertBefore(script,append_to.firstChild); |
|
209 } |
|
210 },0); |
|
211 } |
|
212 |
|
213 // create a clean instance of $LAB |
|
214 function create_sandbox() { |
|
215 var global_defaults = {}, |
|
216 can_use_preloading = real_preloading || xhr_or_cache_preloading, |
|
217 queue = [], |
|
218 registry = {}, |
|
219 instanceAPI |
|
220 ; |
|
221 |
|
222 // global defaults |
|
223 global_defaults[_UseLocalXHR] = true; |
|
224 global_defaults[_AlwaysPreserveOrder] = false; |
|
225 global_defaults[_AllowDuplicates] = false; |
|
226 global_defaults[_CacheBust] = false; |
|
227 /*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/ |
|
228 global_defaults[_BasePath] = ""; |
|
229 |
|
230 // execute a script that has been preloaded already |
|
231 function execute_preloaded_script(chain_opts,script_obj,registry_item) { |
|
232 var script; |
|
233 |
|
234 function preload_execute_finished() { |
|
235 if (script != null) { // make sure this only ever fires once |
|
236 script = null; |
|
237 script_executed(registry_item); |
|
238 } |
|
239 } |
|
240 |
|
241 if (registry[script_obj.src].finished) return; |
|
242 if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true; |
|
243 |
|
244 script = registry_item.elem || document.createElement("script"); |
|
245 if (script_obj.type) script.type = script_obj.type; |
|
246 if (script_obj.charset) script.charset = script_obj.charset; |
|
247 create_script_load_listener(script,registry_item,"finished",preload_execute_finished); |
|
248 |
|
249 // script elem was real-preloaded |
|
250 if (registry_item.elem) { |
|
251 registry_item.elem = null; |
|
252 } |
|
253 // script was XHR preloaded |
|
254 else if (registry_item.text) { |
|
255 script.onload = script.onreadystatechange = null; // script injection doesn't fire these events |
|
256 script.text = registry_item.text; |
|
257 } |
|
258 // script was cache-preloaded |
|
259 else { |
|
260 script.src = script_obj.real_src; |
|
261 } |
|
262 append_to.insertBefore(script,append_to.firstChild); |
|
263 |
|
264 // manually fire execution callback for injected scripts, since events don't fire |
|
265 if (registry_item.text) { |
|
266 preload_execute_finished(); |
|
267 } |
|
268 } |
|
269 |
|
270 // process the script request setup |
|
271 function do_script(chain_opts,script_obj,chain_group,preload_this_script) { |
|
272 var registry_item, |
|
273 registry_items, |
|
274 ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); }, |
|
275 finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); } |
|
276 ; |
|
277 |
|
278 script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]); |
|
279 script_obj.real_src = script_obj.src + |
|
280 // append cache-bust param to URL? |
|
281 (chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "") |
|
282 ; |
|
283 |
|
284 if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false}; |
|
285 registry_items = registry[script_obj.src].items; |
|
286 |
|
287 // allowing duplicates, or is this the first recorded load of this script? |
|
288 if (chain_opts[_AllowDuplicates] || registry_items.length == 0) { |
|
289 registry_item = registry_items[registry_items.length] = { |
|
290 ready:false, |
|
291 finished:false, |
|
292 ready_listeners:[ready_cb], |
|
293 finished_listeners:[finished_cb] |
|
294 }; |
|
295 |
|
296 request_script(chain_opts,script_obj,registry_item, |
|
297 // which callback type to pass? |
|
298 ( |
|
299 (preload_this_script) ? // depends on script-preloading |
|
300 function(){ |
|
301 registry_item.ready = true; |
|
302 for (var i=0; i<registry_item.ready_listeners.length; i++) { |
|
303 registry_item.ready_listeners[i](); |
|
304 } |
|
305 registry_item.ready_listeners = []; |
|
306 } : |
|
307 function(){ script_executed(registry_item); } |
|
308 ), |
|
309 // signal if script-preloading should be used or not |
|
310 preload_this_script |
|
311 ); |
|
312 } |
|
313 else { |
|
314 registry_item = registry_items[0]; |
|
315 if (registry_item.finished) { |
|
316 finished_cb(); |
|
317 } |
|
318 else { |
|
319 registry_item.finished_listeners.push(finished_cb); |
|
320 } |
|
321 } |
|
322 } |
|
323 |
|
324 // creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains |
|
325 function create_chain() { |
|
326 var chainedAPI, |
|
327 chain_opts = merge_objs(global_defaults,{}), |
|
328 chain = [], |
|
329 exec_cursor = 0, |
|
330 scripts_currently_loading = false, |
|
331 group |
|
332 ; |
|
333 |
|
334 // called when a script has finished preloading |
|
335 function chain_script_ready(script_obj,exec_trigger) { |
|
336 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
337 script_obj.ready = true; |
|
338 script_obj.exec_trigger = exec_trigger; |
|
339 advance_exec_cursor(); // will only check for 'ready' scripts to be executed |
|
340 } |
|
341 |
|
342 // called when a script has finished executing |
|
343 function chain_script_executed(script_obj,chain_group) { |
|
344 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
345 script_obj.ready = script_obj.finished = true; |
|
346 script_obj.exec_trigger = null; |
|
347 // check if chain group is all finished |
|
348 for (var i=0; i<chain_group.scripts.length; i++) { |
|
349 if (!chain_group.scripts[i].finished) return; |
|
350 } |
|
351 // chain_group is all finished if we get this far |
|
352 chain_group.finished = true; |
|
353 advance_exec_cursor(); |
|
354 } |
|
355 |
|
356 // main driver for executing each part of the chain |
|
357 function advance_exec_cursor() { |
|
358 while (exec_cursor < chain.length) { |
|
359 if (is_func(chain[exec_cursor])) { |
|
360 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/ |
|
361 try { chain[exec_cursor++](); } catch (err) { |
|
362 /*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/ |
|
363 } |
|
364 continue; |
|
365 } |
|
366 else if (!chain[exec_cursor].finished) { |
|
367 if (check_chain_group_scripts_ready(chain[exec_cursor])) continue; |
|
368 break; |
|
369 } |
|
370 exec_cursor++; |
|
371 } |
|
372 // we've reached the end of the chain (so far) |
|
373 if (exec_cursor == chain.length) { |
|
374 scripts_currently_loading = false; |
|
375 group = false; |
|
376 } |
|
377 } |
|
378 |
|
379 // setup next chain script group |
|
380 function init_script_chain_group() { |
|
381 if (!group || !group.scripts) { |
|
382 chain.push(group = {scripts:[],finished:true}); |
|
383 } |
|
384 } |
|
385 |
|
386 // API for $LAB chains |
|
387 chainedAPI = { |
|
388 // start loading one or more scripts |
|
389 script:function(){ |
|
390 for (var i=0; i<arguments.length; i++) { |
|
391 (function(script_obj,script_list){ |
|
392 var splice_args; |
|
393 |
|
394 if (!is_array(script_obj)) { |
|
395 script_list = [script_obj]; |
|
396 } |
|
397 for (var j=0; j<script_list.length; j++) { |
|
398 init_script_chain_group(); |
|
399 script_obj = script_list[j]; |
|
400 |
|
401 if (is_func(script_obj)) script_obj = script_obj(); |
|
402 if (!script_obj) continue; |
|
403 if (is_array(script_obj)) { |
|
404 // set up an array of arguments to pass to splice() |
|
405 splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in |
|
406 splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array |
|
407 [].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice() |
|
408 j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value |
|
409 continue; |
|
410 } |
|
411 if (typeof script_obj == "string") script_obj = {src:script_obj}; |
|
412 script_obj = merge_objs(script_obj,{ |
|
413 ready:false, |
|
414 ready_cb:chain_script_ready, |
|
415 finished:false, |
|
416 finished_cb:chain_script_executed |
|
417 }); |
|
418 group.finished = false; |
|
419 group.scripts.push(script_obj); |
|
420 |
|
421 do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading)); |
|
422 scripts_currently_loading = true; |
|
423 |
|
424 if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait(); |
|
425 } |
|
426 })(arguments[i],arguments[i]); |
|
427 } |
|
428 return chainedAPI; |
|
429 }, |
|
430 // force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding |
|
431 wait:function(){ |
|
432 if (arguments.length > 0) { |
|
433 for (var i=0; i<arguments.length; i++) { |
|
434 chain.push(arguments[i]); |
|
435 } |
|
436 group = chain[chain.length-1]; |
|
437 } |
|
438 else group = false; |
|
439 |
|
440 advance_exec_cursor(); |
|
441 |
|
442 return chainedAPI; |
|
443 } |
|
444 }; |
|
445 |
|
446 // the first chain link API (includes `setOptions` only this first time) |
|
447 return { |
|
448 script:chainedAPI.script, |
|
449 wait:chainedAPI.wait, |
|
450 setOptions:function(opts){ |
|
451 merge_objs(opts,chain_opts); |
|
452 return chainedAPI; |
|
453 } |
|
454 }; |
|
455 } |
|
456 |
|
457 // API for each initial $LAB instance (before chaining starts) |
|
458 instanceAPI = { |
|
459 // main API functions |
|
460 setGlobalDefaults:function(opts){ |
|
461 merge_objs(opts,global_defaults); |
|
462 return instanceAPI; |
|
463 }, |
|
464 setOptions:function(){ |
|
465 return create_chain().setOptions.apply(null,arguments); |
|
466 }, |
|
467 script:function(){ |
|
468 return create_chain().script.apply(null,arguments); |
|
469 }, |
|
470 wait:function(){ |
|
471 return create_chain().wait.apply(null,arguments); |
|
472 }, |
|
473 |
|
474 // built-in queuing for $LAB `script()` and `wait()` calls |
|
475 // useful for building up a chain programmatically across various script locations, and simulating |
|
476 // execution of the chain |
|
477 queueScript:function(){ |
|
478 queue[queue.length] = {type:"script", args:[].slice.call(arguments)}; |
|
479 return instanceAPI; |
|
480 }, |
|
481 queueWait:function(){ |
|
482 queue[queue.length] = {type:"wait", args:[].slice.call(arguments)}; |
|
483 return instanceAPI; |
|
484 }, |
|
485 runQueue:function(){ |
|
486 var $L = instanceAPI, len=queue.length, i=len, val; |
|
487 for (;--i>=0;) { |
|
488 val = queue.shift(); |
|
489 $L = $L[val.type].apply(null,val.args); |
|
490 } |
|
491 return $L; |
|
492 }, |
|
493 |
|
494 // rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB |
|
495 noConflict:function(){ |
|
496 global.$LAB = _$LAB; |
|
497 return instanceAPI; |
|
498 }, |
|
499 |
|
500 // create another clean instance of $LAB |
|
501 sandbox:function(){ |
|
502 return create_sandbox(); |
|
503 } |
|
504 }; |
|
505 |
|
506 return instanceAPI; |
|
507 } |
|
508 |
|
509 // create the main instance of $LAB |
|
510 global.$LAB = create_sandbox(); |
|
511 |
|
512 |
|
513 /* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html |
|
514 NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). |
|
515 |
|
516 The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does |
|
517 proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked |
|
518 document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. |
|
519 For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or |
|
520 fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. |
|
521 */ |
|
522 (function(addEvent,domLoaded,handler){ |
|
523 if (document.readyState == null && document[addEvent]){ |
|
524 document.readyState = "loading"; |
|
525 document[addEvent](domLoaded,handler = function(){ |
|
526 document.removeEventListener(domLoaded,handler,false); |
|
527 document.readyState = "complete"; |
|
528 },false); |
|
529 } |
|
530 })("addEventListener","DOMContentLoaded"); |
|
531 |
|
532 })(this);/* |
|
533 mustache.js — Logic-less templates in JavaScript |
|
534 |
|
535 See http://mustache.github.com/ for more info. |
|
536 */ |
|
537 |
|
538 var Mustache = function() { |
|
539 var Renderer = function() {}; |
|
540 |
|
541 Renderer.prototype = { |
|
542 otag: "{{", |
|
543 ctag: "}}", |
|
544 pragmas: {}, |
|
545 buffer: [], |
|
546 pragmas_implemented: { |
|
547 "IMPLICIT-ITERATOR": true |
|
548 }, |
|
549 context: {}, |
|
550 |
|
551 render: function(template, context, partials, in_recursion) { |
|
552 // reset buffer & set context |
|
553 if(!in_recursion) { |
|
554 this.context = context; |
|
555 this.buffer = []; // TODO: make this non-lazy |
|
556 } |
|
557 |
|
558 // fail fast |
|
559 if(!this.includes("", template)) { |
|
560 if(in_recursion) { |
|
561 return template; |
|
562 } else { |
|
563 this.send(template); |
|
564 return; |
|
565 } |
|
566 } |
|
567 |
|
568 template = this.render_pragmas(template); |
|
569 var html = this.render_section(template, context, partials); |
|
570 if(in_recursion) { |
|
571 return this.render_tags(html, context, partials, in_recursion); |
|
572 } |
|
573 |
|
574 this.render_tags(html, context, partials, in_recursion); |
|
575 }, |
|
576 |
|
577 /* |
|
578 Sends parsed lines |
|
579 */ |
|
580 send: function(line) { |
|
581 if(line !== "") { |
|
582 this.buffer.push(line); |
|
583 } |
|
584 }, |
|
585 |
|
586 /* |
|
587 Looks for %PRAGMAS |
|
588 */ |
|
589 render_pragmas: function(template) { |
|
590 // no pragmas |
|
591 if(!this.includes("%", template)) { |
|
592 return template; |
|
593 } |
|
594 |
|
595 var that = this; |
|
596 var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + |
|
597 this.ctag, "g"); |
|
598 return template.replace(regex, function(match, pragma, options) { |
|
599 if(!that.pragmas_implemented[pragma]) { |
|
600 throw({message: |
|
601 "This implementation of mustache doesn't understand the '" + |
|
602 pragma + "' pragma"}); |
|
603 } |
|
604 that.pragmas[pragma] = {}; |
|
605 if(options) { |
|
606 var opts = options.split("="); |
|
607 that.pragmas[pragma][opts[0]] = opts[1]; |
|
608 } |
|
609 return ""; |
|
610 // ignore unknown pragmas silently |
|
611 }); |
|
612 }, |
|
613 |
|
614 /* |
|
615 Tries to find a partial in the curent scope and render it |
|
616 */ |
|
617 render_partial: function(name, context, partials) { |
|
618 name = this.trim(name); |
|
619 if(!partials || partials[name] === undefined) { |
|
620 throw({message: "unknown_partial '" + name + "'"}); |
|
621 } |
|
622 if(typeof(context[name]) != "object") { |
|
623 return this.render(partials[name], context, partials, true); |
|
624 } |
|
625 return this.render(partials[name], context[name], partials, true); |
|
626 }, |
|
627 |
|
628 /* |
|
629 Renders inverted (^) and normal (#) sections |
|
630 */ |
|
631 render_section: function(template, context, partials) { |
|
632 if(!this.includes("#", template) && !this.includes("^", template)) { |
|
633 return template; |
|
634 } |
|
635 |
|
636 var that = this; |
|
637 // CSW - Added "+?" so it finds the tighest bound, not the widest |
|
638 var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + |
|
639 "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + |
|
640 "\\s*", "mg"); |
|
641 |
|
642 // for each {{#foo}}{{/foo}} section do... |
|
643 return template.replace(regex, function(match, type, name, content) { |
|
644 var value = that.find(name, context); |
|
645 if(type == "^") { // inverted section |
|
646 if(!value || that.is_array(value) && value.length === 0) { |
|
647 // false or empty list, render it |
|
648 return that.render(content, context, partials, true); |
|
649 } else { |
|
650 return ""; |
|
651 } |
|
652 } else if(type == "#") { // normal section |
|
653 if(that.is_array(value)) { // Enumerable, Let's loop! |
|
654 return that.map(value, function(row) { |
|
655 return that.render(content, that.create_context(row), |
|
656 partials, true); |
|
657 }).join(""); |
|
658 } else if(that.is_object(value)) { // Object, Use it as subcontext! |
|
659 return that.render(content, that.create_context(value), |
|
660 partials, true); |
|
661 } else if(typeof value === "function") { |
|
662 // higher order section |
|
663 return value.call(context, content, function(text) { |
|
664 return that.render(text, context, partials, true); |
|
665 }); |
|
666 } else if(value) { // boolean section |
|
667 return that.render(content, context, partials, true); |
|
668 } else { |
|
669 return ""; |
|
670 } |
|
671 } |
|
672 }); |
|
673 }, |
|
674 |
|
675 /* |
|
676 Replace {{foo}} and friends with values from our view |
|
677 */ |
|
678 render_tags: function(template, context, partials, in_recursion) { |
|
679 // tit for tat |
|
680 var that = this; |
|
681 |
|
682 var new_regex = function() { |
|
683 return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + |
|
684 that.ctag + "+", "g"); |
|
685 }; |
|
686 |
|
687 var regex = new_regex(); |
|
688 var tag_replace_callback = function(match, operator, name) { |
|
689 switch(operator) { |
|
690 case "!": // ignore comments |
|
691 return ""; |
|
692 case "=": // set new delimiters, rebuild the replace regexp |
|
693 that.set_delimiters(name); |
|
694 regex = new_regex(); |
|
695 return ""; |
|
696 case ">": // render partial |
|
697 return that.render_partial(name, context, partials); |
|
698 case "{": // the triple mustache is unescaped |
|
699 return that.find(name, context); |
|
700 default: // escape the value |
|
701 return that.escape(that.find(name, context)); |
|
702 } |
|
703 }; |
|
704 var lines = template.split("\n"); |
|
705 for(var i = 0; i < lines.length; i++) { |
|
706 lines[i] = lines[i].replace(regex, tag_replace_callback, this); |
|
707 if(!in_recursion) { |
|
708 this.send(lines[i]); |
|
709 } |
|
710 } |
|
711 |
|
712 if(in_recursion) { |
|
713 return lines.join("\n"); |
|
714 } |
|
715 }, |
|
716 |
|
717 set_delimiters: function(delimiters) { |
|
718 var dels = delimiters.split(" "); |
|
719 this.otag = this.escape_regex(dels[0]); |
|
720 this.ctag = this.escape_regex(dels[1]); |
|
721 }, |
|
722 |
|
723 escape_regex: function(text) { |
|
724 // thank you Simon Willison |
|
725 if(!arguments.callee.sRE) { |
|
726 var specials = [ |
|
727 '/', '.', '*', '+', '?', '|', |
|
728 '(', ')', '[', ']', '{', '}', '\\' |
|
729 ]; |
|
730 arguments.callee.sRE = new RegExp( |
|
731 '(\\' + specials.join('|\\') + ')', 'g' |
|
732 ); |
|
733 } |
|
734 return text.replace(arguments.callee.sRE, '\\$1'); |
|
735 }, |
|
736 |
|
737 /* |
|
738 find `name` in current `context`. That is find me a value |
|
739 from the view object |
|
740 */ |
|
741 find: function(name, context) { |
|
742 name = this.trim(name); |
|
743 |
|
744 // Checks whether a value is thruthy or false or 0 |
|
745 function is_kinda_truthy(bool) { |
|
746 return bool === false || bool === 0 || bool; |
|
747 } |
|
748 |
|
749 var value; |
|
750 if(is_kinda_truthy(context[name])) { |
|
751 value = context[name]; |
|
752 } else if(is_kinda_truthy(this.context[name])) { |
|
753 value = this.context[name]; |
|
754 } |
|
755 |
|
756 if(typeof value === "function") { |
|
757 return value.apply(context); |
|
758 } |
|
759 if(value !== undefined) { |
|
760 return value; |
|
761 } |
|
762 // silently ignore unkown variables |
|
763 return ""; |
|
764 }, |
|
765 |
|
766 // Utility methods |
|
767 |
|
768 /* includes tag */ |
|
769 includes: function(needle, haystack) { |
|
770 return haystack.indexOf(this.otag + needle) != -1; |
|
771 }, |
|
772 |
|
773 /* |
|
774 Does away with nasty characters |
|
775 */ |
|
776 escape: function(s) { |
|
777 s = String(s === null ? "" : s); |
|
778 return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { |
|
779 switch(s) { |
|
780 case "&": return "&"; |
|
781 case "\\": return "\\\\"; |
|
782 case '"': return '"'; |
|
783 case "'": return '''; |
|
784 case "<": return "<"; |
|
785 case ">": return ">"; |
|
786 default: return s; |
|
787 } |
|
788 }); |
|
789 }, |
|
790 |
|
791 // by @langalex, support for arrays of strings |
|
792 create_context: function(_context) { |
|
793 if(this.is_object(_context)) { |
|
794 return _context; |
|
795 } else { |
|
796 var iterator = "."; |
|
797 if(this.pragmas["IMPLICIT-ITERATOR"]) { |
|
798 iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; |
|
799 } |
|
800 var ctx = {}; |
|
801 ctx[iterator] = _context; |
|
802 return ctx; |
|
803 } |
|
804 }, |
|
805 |
|
806 is_object: function(a) { |
|
807 return a && typeof a == "object"; |
|
808 }, |
|
809 |
|
810 is_array: function(a) { |
|
811 return Object.prototype.toString.call(a) === '[object Array]'; |
|
812 }, |
|
813 |
|
814 /* |
|
815 Gets rid of leading and trailing whitespace |
|
816 */ |
|
817 trim: function(s) { |
|
818 return s.replace(/^\s*|\s*$/g, ""); |
|
819 }, |
|
820 |
|
821 /* |
|
822 Why, why, why? Because IE. Cry, cry cry. |
|
823 */ |
|
824 map: function(array, fn) { |
|
825 if (typeof array.map == "function") { |
|
826 return array.map(fn); |
|
827 } else { |
|
828 var r = []; |
|
829 var l = array.length; |
|
830 for(var i = 0; i < l; i++) { |
|
831 r.push(fn(array[i])); |
|
832 } |
|
833 return r; |
|
834 } |
|
835 } |
|
836 }; |
|
837 |
|
838 return({ |
|
839 name: "mustache.js", |
|
840 version: "0.3.1-dev", |
|
841 |
|
842 /* |
|
843 Turns a template and view into HTML |
|
844 */ |
|
845 to_html: function(template, view, partials, send_fun) { |
|
846 var renderer = new Renderer(); |
|
847 if(send_fun) { |
|
848 renderer.send = send_fun; |
|
849 } |
|
850 renderer.render(template, view, partials); |
|
851 if(!send_fun) { |
|
852 return renderer.buffer.join("\n"); |
|
853 } |
|
854 } |
|
855 }); |
|
856 }(); |
|
857 // Underscore.js 1.2.3 |
|
858 // (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc. |
|
859 // Underscore is freely distributable under the MIT license. |
|
860 // Portions of Underscore are inspired or borrowed from Prototype, |
|
861 // Oliver Steele's Functional, and John Resig's Micro-Templating. |
|
862 // For all details and documentation: |
|
863 // http://documentcloud.github.com/underscore |
|
864 (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== |
|
865 c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, |
|
866 h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.concat,H=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,I=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&& |
|
867 define.amd?define("underscore",function(){return b}):s._=b;b.VERSION="1.2.3";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a, |
|
868 c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g, |
|
869 c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c, |
|
870 b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});return e};var D=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a, |
|
871 d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a, |
|
872 computed:b})});return e.value};b.shuffle=function(a){var c=[],b;j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex= |
|
873 function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length- |
|
874 1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d}, |
|
875 []);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a, |
|
876 c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g}; |
|
877 var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a, |
|
878 c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true: |
|
879 a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=G.apply([a],arguments);return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after= |
|
880 function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=I||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments, |
|
881 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a=== |
|
882 Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)== |
|
883 "[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){J(c, |
|
884 b[c]=a[c])})};var K=0;b.uniqueId=function(a){var b=K++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g, |
|
885 "'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},J=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return u(c.apply(b, |
|
886 a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); |
|
887 /* main file */ |
|
888 |
|
889 if ( window.IriSP === undefined && window.__IriSP === undefined ) { |
|
890 var IriSP = {}; |
|
891 var __IriSP = IriSP; /* for backward compatibility */ |
|
892 } |
|
893 |
|
894 IriSP.loadLibs = function( libs, config, metadata_url, callback ) { |
|
895 // Localize jQuery variable |
|
896 IriSP.jQuery = null; |
|
897 var $L = $LAB.script(libs.jQuery).script(libs.swfObject) |
|
898 .script(libs.jQueryUI) |
|
899 |
|
900 if (config.player.type === "jwplayer") { |
|
901 // load our popcorn.js lookalike |
|
902 $L = $L.script(libs.jwplayer); |
|
903 } else { |
|
904 // load the real popcorn |
|
905 $L = $L.script(libs.popcorn).script(libs["popcorn.code"]); |
|
906 if (config.player.type === "youtube") { |
|
907 $L = $L.script(libs["popcorn.youtube"]); |
|
908 } |
|
909 if (config.player.type === "vimeo") |
|
910 $L = $L.script(libs["popcorn.vimeo"]); |
|
911 |
|
912 /* do nothing for html5 */ |
|
913 } |
|
914 |
|
915 /* widget specific requirements */ |
|
916 for (var idx in config.gui.widgets) { |
|
917 if (config.gui.widgets[idx].type === "PolemicWidget") { |
|
918 $L.script(libs.raphael); |
|
919 } |
|
920 } |
|
921 |
|
922 // same for modules |
|
923 /* |
|
924 for (var idx in config.modules) { |
|
925 if (config.modules[idx].type === "PolemicWidget") |
|
926 $L.script(libs.raphaelJs); |
|
927 } |
|
928 */ |
|
929 |
|
930 $L.wait(function() { |
|
931 IriSP.jQuery = window.jQuery.noConflict( true ); |
|
932 IriSP._ = window._.noConflict(); |
|
933 IriSP.underscore = IriSP._; |
|
934 |
|
935 var css_link_jquery = IriSP.jQuery( "<link>", { |
|
936 rel: "stylesheet", |
|
937 type: "text/css", |
|
938 href: libs.cssjQueryUI, |
|
939 'class': "dynamic_css" |
|
940 } ); |
|
941 var css_link_custom = IriSP.jQuery( "<link>", { |
|
942 rel: "stylesheet", |
|
943 type: "text/css", |
|
944 href: config.gui.css, |
|
945 'class': "dynamic_css" |
|
946 } ); |
|
947 |
|
948 css_link_jquery.appendTo('head'); |
|
949 css_link_custom.appendTo('head'); |
|
950 |
|
951 IriSP.setupDataLoader(); |
|
952 IriSP.__dataloader.get(metadata_url, |
|
953 function(data) { |
|
954 /* save the data so that we could re-use it to |
|
955 configure the video |
|
956 */ |
|
957 IriSP.__jsonMetadata = data; |
|
958 callback.call(window) }); |
|
959 }); |
|
960 }; |
|
961 IriSP.annotation_template = "{{! template for an annotation displayed in a segmentWidget }}<div title='{{divTitle}}' id='{{id}}' class='Ldt-iri-chapter' style='left: {{startPixel}}px; width: {{pxWidth}}px; background-color:#{{hexa_color}};' ></div>"; |
|
962 IriSP.annotationWidget_template = "{{! template for the annotation widget }}<div class='Ldt-AnnotationsWidget'> <!-- ugly div because we want to have a double border --> <div class='Ldt-Annotation-DoubleBorder'> <div class='Ldt-AnnotationContent'> <div class='Ldt-AnnotationShareIcons'> <a class='Ldt-fbShare' href=''><img src='{{img_dir}}/facebook.png' alt='share on facebook'></img></a> <a class='Ldt-TwShare' href=''><img src='{{img_dir}}/twitter.png' alt='share on twitter'></img></a> <a class='Ldt-GplusShare' href=''><img src='{{img_dir}}/google.png' alt='share on google+'></img></a> </div> <div class='Ldt-SaTitle'></div> <div class='Ldt-SaDescription'></div> </div> </div></div>"; |
|
963 IriSP.annotation_loading_template = "{{! template shown while the annotation widget is loading }}<div id='Ldt-load-container'><div id='Ldt-loader'> </div> Chargement... </div>"; |
|
964 IriSP.arrowWidget_template = "<div class='Ldt-arrowWidget'></div>"; |
|
965 IriSP.overlay_marker_template = "{{! the template for the small bars which is z-indexed over our segment widget }}<div class='Ldt-SegmentPositionMarker' style='background-color: #F7268E;'></div>"; |
|
966 IriSP.player_template = "{{! template for the radio player }}<div class='Ldt-controler demo'> <div class='Ldt-LeftPlayerControls'> <div class='Ldt-button Ldt-CtrlPlay'></div> <div class='Ldt-button Ldt-CtrlAnnotate'></div> <div class='Ldt-button Ldt-CtrlSearch'></div> </div> <div class='Ldt-RightPlayerControls'> <div class='Ldt-Time'> <div class='Ldt-ElapsedTime'></div> <div class='Ldt-TimeSeparator'>/</div> <div class='Ldt-TotalTime'></div> </div> <div class='Ldt-button Ldt-CtrlSound'></div> </div></div>"; |
|
967 IriSP.search_template = "{{! template for the search container }}<div class='LdtSearchContainer' style='margin-left: {{margin_left}}; position: absolute; margin-top: -60px;'> <div class='LdtSearch' style='display: none; background-color: #EEE; width: 165px; boder: 1px; border-color: #CFCFCF; position: absolute; text-align: center;'> <input class='LdtSearchInput' style='margin-top: 2px; margin-bottom: 2px;' /> </div></div><div class='cleaner'></div>"; |
|
968 IriSP.share_template = "{{! social network sharing template }}<a onclick='__IriSP.MyApiPlayer.share(\'delicious\');' title='partager avec delicious'><span class='share shareDelicious'> </span></a> <a onclick='__IriSP.MyApiPlayer.share(\'facebook\');' title='partager avec facebook'> <span class='share shareFacebook'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'twitter\');' title='partager avec twitter'> <span class='share shareTwitter'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'myspace\');' title='partager avec Myspace'> <span class='share shareMySpace'> </span></a>"; |
|
969 IriSP.sliderWidget_template = "{{! template for the slider widget - it's composed of two divs we one overlayed on top of the other }}<div class='Ldt-sliderBackground'></div><div class='Ldt-sliderForeground'></div><div class='Ldt-sliderPositionMarker'></div>"; |
|
970 IriSP.tooltip_template = "{{! template used by the jquery ui tooltip }}<div class='Ldt-tooltip'> <div class='title'>{{title}}</div> <div class='time'>{{begin}} : {{end}} </div> <div class='description'>{{description}}</div></div>"; |
|
971 IriSP.tooltipWidget_template = "{{! template for the tooltip widget }}<div class='tip'> <div class='tipcolor' style='height:10px;width:10px'></div> <div class='tiptext'></div>"; |
|
972 IriSP.tweetWidget_template = "{{! template for the tweet widget }}<div class='Ldt-tweetWidget'> <div class='Ldt-tweet-DoubleBorder'> <img src='{{img_dir}}/minimize.png' class='Ldt-tweetWidgetKeepOpen' alt='dont minimize automatically'></img> <img src='{{img_dir}}/minimize.png' class='Ldt-tweetWidgetMinimize' alt='minimize window'></img> <div class='Ldt-tweetAvatar'></div> <img src='{{img_dir}}/profile_arrow.png' class='Ldt-tweetAvatar-profileArrow'></img> <div class='Ldt-tweetContents'></div> <a href='' target='_blank' class='Ldt-Retweet'><div class='Ldt-RetweetIcon'></div> - Retweet </a> <a href='' target='_blank' class='Ldt-TweetReply'><div class='Ldt-TweetReplyIcon'></div> - Reply</a> </div></div>";/* wrapper that simulates popcorn.js because |
|
973 popcorn is a bit unstable at the time */ |
|
974 |
|
975 IriSP.PopcornReplacement = { |
|
976 msgPump : {} /* used by jquery to receive and send messages */ |
|
977 }; |
|
978 |
|
979 IriSP.PopcornReplacement.media = { |
|
980 "paused": true, |
|
981 "muted": false |
|
982 }; |
|
983 |
|
984 IriSP.PopcornReplacement.listen = function(msg, callback) { |
|
985 // IriSP.jQuery(IriSP.PopcornReplacement.msgPump).bind(msg, function(event, rest) { callback(rest); }); |
|
986 if (!IriSP.PopcornReplacement.msgPump.hasOwnProperty(msg)) |
|
987 IriSP.PopcornReplacement.msgPump[msg] = []; |
|
988 |
|
989 IriSP.PopcornReplacement.msgPump[msg].push(callback); |
|
990 }; |
|
991 |
|
992 IriSP.PopcornReplacement.trigger = function(msg, params) { |
|
993 // IriSP.jQuery(IriSP.PopcornReplacement.msgPump).trigger(msg, params); |
|
994 |
|
995 if (!IriSP.PopcornReplacement.msgPump.hasOwnProperty(msg)) |
|
996 return; |
|
997 |
|
998 var d = IriSP.PopcornReplacement.msgPump[msg]; |
|
999 for(var entry in d) { |
|
1000 d[entry].call(window, params); |
|
1001 } |
|
1002 |
|
1003 }; |
|
1004 |
|
1005 IriSP.PopcornReplacement.guid = function(prefix) { |
|
1006 var str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
1007 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); |
|
1008 return v.toString(16); |
|
1009 }); |
|
1010 |
|
1011 return prefix + str; |
|
1012 }; |
|
1013 |
|
1014 IriSP.PopcornReplacement.__initApi = function() { |
|
1015 IriSP.PopcornReplacement.trigger("loadedmetadata"); // we've done more than loading metadata of course, |
|
1016 // but popcorn doesn't need to know more. |
|
1017 IriSP.PopcornReplacement.media.muted = jwplayer(IriSP.PopcornReplacement._container).getMute(); |
|
1018 }; |
|
1019 |
|
1020 IriSP.PopcornReplacement.jwplayer = function(container, options) { |
|
1021 IriSP.PopcornReplacement._container = container.slice(1); //eschew the '#' |
|
1022 options.events = { |
|
1023 onReady: IriSP.PopcornReplacement.__initApi, |
|
1024 onTime: IriSP.PopcornReplacement.__timeHandler, |
|
1025 onPlay: IriSP.PopcornReplacement.__playHandler, |
|
1026 onPause: IriSP.PopcornReplacement.__pauseHandler, |
|
1027 onSeek: IriSP.PopcornReplacement.__seekHandler |
|
1028 } |
|
1029 |
|
1030 jwplayer(IriSP.PopcornReplacement._container).setup(options); |
|
1031 IriSP.PopcornReplacement.media.duration = options.duration; |
|
1032 return IriSP.PopcornReplacement; |
|
1033 }; |
|
1034 |
|
1035 IriSP.PopcornReplacement.currentTime = function(time) { |
|
1036 if (typeof(time) === "undefined") { |
|
1037 return jwplayer(IriSP.PopcornReplacement._container).getPosition(); |
|
1038 } else { |
|
1039 var currentTime = +time; |
|
1040 jwplayer( IriSP.PopcornReplacement._container ).seek( currentTime ); |
|
1041 IriSP.PopcornReplacement.trigger("seeked"); |
|
1042 return jwplayer(IriSP.PopcornReplacement._container).getPosition(); |
|
1043 } |
|
1044 }; |
|
1045 |
|
1046 IriSP.PopcornReplacement.play = function() { |
|
1047 IriSP.PopcornReplacement.media.paused = false; |
|
1048 IriSP.PopcornReplacement.trigger("play"); |
|
1049 // IriSP.PopcornReplacement.trigger("playing"); |
|
1050 jwplayer( IriSP.PopcornReplacement._container ).play(); |
|
1051 }; |
|
1052 |
|
1053 IriSP.PopcornReplacement.pause = function() { |
|
1054 if ( !IriSP.PopcornReplacement.media.paused ) { |
|
1055 IriSP.PopcornReplacement.media.paused = true; |
|
1056 IriSP.PopcornReplacement.trigger( "pause" ); |
|
1057 jwplayer( IriSP.PopcornReplacement._container ).pause(); |
|
1058 } |
|
1059 }; |
|
1060 |
|
1061 IriSP.PopcornReplacement.muted = function(val) { |
|
1062 if (typeof(val) !== "undefined") { |
|
1063 |
|
1064 if (jwplayer(IriSP.PopcornReplacement._container).getMute() !== val) { |
|
1065 if (val) { |
|
1066 jwplayer(IriSP.PopcornReplacement._container).setMute(true); |
|
1067 IriSP.PopcornReplacement.media.muted = true; |
|
1068 } else { |
|
1069 jwplayer( IriSP.PopcornReplacement._container ).setMute(false); |
|
1070 IriSP.PopcornReplacement.media.muted = false; |
|
1071 } |
|
1072 |
|
1073 IriSP.PopcornReplacement.trigger( "volumechange" ); |
|
1074 } |
|
1075 |
|
1076 return jwplayer( IriSP.PopcornReplacement._container ).getMute(); |
|
1077 } else { |
|
1078 return jwplayer( IriSP.PopcornReplacement._container ).getMute(); |
|
1079 } |
|
1080 }; |
|
1081 |
|
1082 IriSP.PopcornReplacement.mute = IriSP.PopcornReplacement.muted; |
|
1083 |
|
1084 IriSP.PopcornReplacement.__codes = []; |
|
1085 IriSP.PopcornReplacement.code = function(options) { |
|
1086 IriSP.PopcornReplacement.__codes.push(options); |
|
1087 return IriSP.PopcornReplacement; |
|
1088 }; |
|
1089 |
|
1090 IriSP.PopcornReplacement.__runCode = function() { |
|
1091 var currentTime = jwplayer(IriSP.PopcornReplacement._container).getPosition(); |
|
1092 var i = 0; |
|
1093 for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) { |
|
1094 var c = IriSP.PopcornReplacement.__codes[i]; |
|
1095 if (currentTime == c.start) { |
|
1096 c.onStart(); |
|
1097 } |
|
1098 |
|
1099 if (currentTime == c.end) { |
|
1100 c.onEnd(); |
|
1101 } |
|
1102 |
|
1103 } |
|
1104 }; |
|
1105 |
|
1106 /* called everytime the player updates itself |
|
1107 (onTime event) |
|
1108 */ |
|
1109 |
|
1110 IriSP.PopcornReplacement.__timeHandler = function(event) { |
|
1111 var pos = event.position; |
|
1112 |
|
1113 var i = 0; |
|
1114 for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) { |
|
1115 var c = IriSP.PopcornReplacement.__codes[i]; |
|
1116 |
|
1117 if (pos >= c.start && pos < c.end && |
|
1118 pos - 0.1 <= c.start) { |
|
1119 c.onStart(); |
|
1120 } |
|
1121 |
|
1122 if (pos > c.start && pos > c.end && |
|
1123 pos - 0.1 <= c.end) { |
|
1124 console.log("eonedn"); |
|
1125 c.onEnd(); |
|
1126 } |
|
1127 |
|
1128 } |
|
1129 |
|
1130 IriSP.PopcornReplacement.trigger("timeupdate"); |
|
1131 }; |
|
1132 |
|
1133 IriSP.PopcornReplacement.__seekHandler = function(event) { |
|
1134 var i = 0; |
|
1135 |
|
1136 for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) { |
|
1137 var c = IriSP.PopcornReplacement.__codes[i]; |
|
1138 |
|
1139 if (event.position >= c.start && event.position < c.end) { |
|
1140 c.onEnd(); |
|
1141 } |
|
1142 } |
|
1143 |
|
1144 for(i = 0; i < IriSP.PopcornReplacement.__codes.length; i++) { |
|
1145 var c = IriSP.PopcornReplacement.__codes[i]; |
|
1146 |
|
1147 if (typeof(event.offset) === "undefined") |
|
1148 event.offset = 0; |
|
1149 |
|
1150 if (event.offset >= c.start && event.offset < c.end) { |
|
1151 c.onStart(); |
|
1152 } |
|
1153 |
|
1154 } |
|
1155 |
|
1156 IriSP.PopcornReplacement.trigger("timeupdate"); |
|
1157 }; |
|
1158 |
|
1159 |
|
1160 IriSP.PopcornReplacement.__playHandler = function(event) { |
|
1161 IriSP.PopcornReplacement.media.paused = false; |
|
1162 IriSP.PopcornReplacement.trigger("play"); |
|
1163 }; |
|
1164 |
|
1165 IriSP.PopcornReplacement.__pauseHandler = function(event) { |
|
1166 IriSP.PopcornReplacement.media.paused = true; |
|
1167 IriSP.PopcornReplacement.trigger("pause"); |
|
1168 }; |
|
1169 |
|
1170 IriSP.PopcornReplacement.roundTime = function() { |
|
1171 var currentTime = IriSP.PopcornReplacement.currentTime(); |
|
1172 return Math.round(currentTime); |
|
1173 }; |
|
1174 /* utils.js - various utils that don't belong anywhere else */ |
|
1175 |
|
1176 /* trace function, for debugging */ |
|
1177 |
|
1178 IriSP.traceNum = 0; |
|
1179 IriSP.trace = function( msg, value ) { |
|
1180 /* |
|
1181 if( IriSP.config.gui.debug === true ) { |
|
1182 IriSP.traceNum += 1; |
|
1183 IriSP.jQuery( "<div>"+IriSP.traceNum+" - "+msg+" : "+value+"</div>" ).appendTo( "#Ldt-output" ); |
|
1184 } |
|
1185 */ |
|
1186 }; |
|
1187 |
|
1188 /* used in callbacks - because in callbacks we lose "this", |
|
1189 we need to have a special function which wraps "this" in |
|
1190 a closure. This way, the |
|
1191 */ |
|
1192 IriSP.wrap = function (obj, fn) { |
|
1193 return function() { |
|
1194 var args = Array.prototype.slice.call(arguments, 0); |
|
1195 return fn.apply(obj, args); |
|
1196 } |
|
1197 } |
|
1198 |
|
1199 /* convert a time to a percentage in the media */ |
|
1200 IriSP.timeToPourcent = function(time, timetotal){ |
|
1201 var time = Math.abs(time); |
|
1202 var timetotal = Math.abs(timetotal); |
|
1203 |
|
1204 return Math.floor((time/timetotal) * 100); |
|
1205 }; |
|
1206 |
|
1207 IriSP.padWithZeros = function(num) { |
|
1208 if (Math.abs(num) < 10) { |
|
1209 return "0" + num.toString(); |
|
1210 } else { |
|
1211 return num.toString(); |
|
1212 } |
|
1213 }; |
|
1214 /* convert a number of seconds to a tuple of the form |
|
1215 [hours, minutes, seconds] |
|
1216 */ |
|
1217 IriSP.secondsToTime = function(secs) { |
|
1218 var hours = Math.abs(parseInt( secs / 3600 ) % 24); |
|
1219 var minutes = Math.abs(parseInt( secs / 60 ) % 60); |
|
1220 var seconds = parseFloat(Math.abs(secs % 60).toFixed(0)); |
|
1221 |
|
1222 var toString_fn = function() { |
|
1223 var ret = ""; |
|
1224 if (hours > 0) |
|
1225 ret = IriSP.padWithZeros(this.hours) + ":"; |
|
1226 ret += IriSP.padWithZeros(this.minutes) + ":" + IriSP.padWithZeros(this.seconds); |
|
1227 |
|
1228 return ret; |
|
1229 } |
|
1230 return {"hours" : hours, "minutes" : minutes, "seconds" : seconds, toString: toString_fn}; |
|
1231 }; |
|
1232 |
|
1233 IriSP.secondsToString |
|
1234 |
|
1235 /* format a tweet - replaces @name by a link to the profile, #hashtag, etc. */ |
|
1236 IriSP.formatTweet = function(tweet) { |
|
1237 /* |
|
1238 an array of arrays which hold a regexp and its replacement. |
|
1239 */ |
|
1240 var regExps = [ |
|
1241 /* copied from http://codegolf.stackexchange.com/questions/464/shortest-url-regex-match-in-javascript/480#480 */ |
|
1242 [/((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi, "<a href='$1'>$1</a>"], |
|
1243 [/@(\w+)/gi, "<a href='http://twitter.com/$1'>@$1</a>"], // matches a @handle |
|
1244 [/#(\w+)/gi, "<a href='http://twitter.com/search?q=%23$1'>#$1</a>"],// matches a hashtag |
|
1245 [/(\+\+)/gi, "<span class='Ldt-PolemicPlusPlus'>$1</span>"], |
|
1246 [/(--)/gi, "<span class='Ldt-PolemicMinusMinus'>$1</span>"], |
|
1247 [/(==)/gi, "<span class='Ldt-PolemicEqualEqual'>$1</span>"], |
|
1248 [/(\?\?)/gi, "<span class='Ldt-PolemicQuestion'>$1</span>"] |
|
1249 ]; |
|
1250 |
|
1251 var i = 0; |
|
1252 for(i = 0; i < regExps.length; i++) { |
|
1253 tweet = tweet.replace(regExps[i][0], regExps[i][1]); |
|
1254 } |
|
1255 |
|
1256 return tweet; |
|
1257 }; |
|
1258 |
|
1259 IriSP.countProperties = function(obj) { |
|
1260 var count = 0; |
|
1261 |
|
1262 for(var prop in obj) { |
|
1263 if(obj.hasOwnProperty(prop)) |
|
1264 ++count; |
|
1265 } |
|
1266 |
|
1267 return count; |
|
1268 }; |
|
1269 |
|
1270 // conversion de couleur Decimal vers HexaDecimal || 000 si fff |
|
1271 IriSP.DEC_HEXA_COLOR = function (dec) { |
|
1272 var hexa='0123456789ABCDEF'; |
|
1273 var hex=''; |
|
1274 var tmp; |
|
1275 while (dec>15){ |
|
1276 tmp = dec-(Math.floor(dec/16))*16; |
|
1277 hex = hexa.charAt(tmp)+hex; |
|
1278 dec = Math.floor(dec/16); |
|
1279 } |
|
1280 hex = hexa.charAt(dec)+hex; |
|
1281 return(hex); |
|
1282 }; |
|
1283 |
|
1284 /* shortcut to have global variables in templates */ |
|
1285 IriSP.templToHTML = function(template, values) { |
|
1286 var params = IriSP.jQuery.extend(IriSP.default_templates_vars, values); |
|
1287 return Mustache.to_html(template, params); |
|
1288 }; |
|
1289 |
|
1290 /* we need to be stricter than encodeURIComponent, |
|
1291 because of twitter |
|
1292 */ |
|
1293 IriSP.encodeURI = function(str) { |
|
1294 return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28'). |
|
1295 replace(/\)/g, '%29').replace(/\*/g, '%2A'); |
|
1296 } |
|
1297 |
|
1298 IriSP.__guidCounter = 0; |
|
1299 IriSP.guid = function(prefix) { |
|
1300 IriSP.__guidCounter += 1; |
|
1301 return prefix + IriSP.__guidCounter; |
|
1302 }; |
|
1303 /* for ie compatibility |
|
1304 if (Object.prototype.__defineGetter__&&!Object.defineProperty) { |
|
1305 Object.defineProperty=function(obj,prop,desc) { |
|
1306 if ("get" in desc) obj.__defineGetter__(prop,desc.get); |
|
1307 if ("set" in desc) obj.__defineSetter__(prop,desc.set); |
|
1308 } |
|
1309 } |
|
1310 */ |
|
1311 /* data.js - this file deals with how the players gets and sends data */ |
|
1312 |
|
1313 IriSP.DataLoader = function() { |
|
1314 this._cache = {}; |
|
1315 |
|
1316 /* |
|
1317 A structure to hold callbacks for specific urls. We need it because |
|
1318 ajax calls are asynchronous, so it means that sometimes we ask |
|
1319 multiple times for a ressource because the first call hasn't been |
|
1320 received yet. |
|
1321 */ |
|
1322 this._callbacks = {}; |
|
1323 }; |
|
1324 |
|
1325 IriSP.DataLoader.prototype.get = function(url, callback) { |
|
1326 |
|
1327 var base_url = url.split("&")[0] |
|
1328 if (this._cache.hasOwnProperty(base_url)) { |
|
1329 callback(this._cache[base_url]); |
|
1330 } else { |
|
1331 if (!this._callbacks.hasOwnProperty(base_url)) { |
|
1332 this._callbacks[base_url] = []; |
|
1333 this._callbacks[base_url].push(callback); |
|
1334 /* we need a closure because this gets lost when it's called back */ |
|
1335 |
|
1336 // uncomment you don't want to use caching. |
|
1337 // IriSP.jQuery.get(url, callback); |
|
1338 |
|
1339 var func = function(data) { |
|
1340 this._cache[base_url] = data; |
|
1341 var i = 0; |
|
1342 |
|
1343 for (i = 0; i < this._callbacks[base_url].length; i++) { |
|
1344 this._callbacks[base_url][i](this._cache[base_url]); |
|
1345 } |
|
1346 }; |
|
1347 |
|
1348 /* automagically choose between json and jsonp */ |
|
1349 if (url.indexOf(document.location.hostname) === -1 && |
|
1350 url.indexOf("http://") !== -1 /* not a relative url */ ) { |
|
1351 // we contacting a foreign domain, use JSONP |
|
1352 |
|
1353 IriSP.jQuery.get(url, {}, IriSP.wrap(this, func), "jsonp"); |
|
1354 } else { |
|
1355 |
|
1356 // otherwise, hey, whatever rows your boat |
|
1357 IriSP.jQuery.get(url, IriSP.wrap(this, func)); |
|
1358 } |
|
1359 |
|
1360 } else { |
|
1361 /* simply push the callback - it'll get called when the ressource |
|
1362 has been received */ |
|
1363 |
|
1364 this._callbacks[base_url].push(callback); |
|
1365 |
|
1366 } |
|
1367 } |
|
1368 } |
|
1369 |
|
1370 /* the base abstract "class" */ |
|
1371 IriSP.Serializer = function(DataLoader, url) { |
|
1372 this._DataLoader = DataLoader; |
|
1373 this._url = url; |
|
1374 this._data = []; |
|
1375 }; |
|
1376 |
|
1377 IriSP.Serializer.prototype.serialize = function(data) { }; |
|
1378 IriSP.Serializer.prototype.deserialize = function(data) {}; |
|
1379 |
|
1380 IriSP.Serializer.prototype.currentMedia = function() { |
|
1381 }; |
|
1382 |
|
1383 IriSP.Serializer.prototype.sync = function(callback) { |
|
1384 callback.call(this, this._data); |
|
1385 }; |
|
1386 |
|
1387 IriSP.SerializerFactory = function(DataLoader) { |
|
1388 this._dataloader = DataLoader; |
|
1389 }; |
|
1390 |
|
1391 IriSP.SerializerFactory.prototype.getSerializer = function(metadataOptions) { |
|
1392 /* This function returns serializer set-up with the correct |
|
1393 configuration - takes a metadata struct describing the metadata source |
|
1394 */ |
|
1395 |
|
1396 if (metadataOptions === undefined) |
|
1397 /* return an empty serializer */ |
|
1398 return IriSP.Serializer("", ""); |
|
1399 |
|
1400 switch(metadataOptions.type) { |
|
1401 case "json": |
|
1402 return new IriSP.JSONSerializer(this._dataloader, metadataOptions.src); |
|
1403 break; |
|
1404 |
|
1405 case "dummy": /* only used for unit testing - not defined in production */ |
|
1406 return new IriSP.MockSerializer(this._dataloader, metadataOptions.src); |
|
1407 break; |
|
1408 |
|
1409 case "empty": |
|
1410 return new IriSP.Serializer("", "empty"); |
|
1411 break; |
|
1412 |
|
1413 default: |
|
1414 return undefined; |
|
1415 } |
|
1416 }; |
|
1417 /* site.js - all our site-dependent config : player chrome, cdn locations, etc...*/ |
|
1418 |
|
1419 IriSP.lib = { |
|
1420 jQuery : "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js", |
|
1421 jQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/jquery-ui.js", |
|
1422 jQueryToolTip : "http://cdn.jquerytools.org/1.2.4/all/jquery.tools.min.js", |
|
1423 swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", |
|
1424 cssjQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/themes/base/jquery-ui.css", |
|
1425 popcorn : "/mdp/src/js/libs/popcorn.js", |
|
1426 jwplayer : "/mdp/src/js/libs/jwplayer.js", |
|
1427 popcornReplacement: "/mdp/src/js/libs/pop.js", |
|
1428 raphael: "/mdp/src/js/libs/raphael.js", |
|
1429 "popcorn.mediafragment" : "/mdp/src/js/libs/popcorn.mediafragment.js", |
|
1430 "popcorn.code" : "/mdp/src/js/libs/popcorn.code.js", |
|
1431 "popcorn.jwplayer": "/mdp/src/js/libs/popcorn.jwplayer.js", |
|
1432 "popcorn.youtube": "/mdp/src/js/libs/popcorn.youtube.js" |
|
1433 }; |
|
1434 |
|
1435 //Player Configuration |
|
1436 IriSP.config = undefined; |
|
1437 |
|
1438 IriSP.widgetsDefaults = { |
|
1439 "LayoutManager" : {spacer_div_height : "0px" }, |
|
1440 "PlayerWidget" : {}, |
|
1441 "AnnotationsWidget": { |
|
1442 "share_text" : "I'm watching ", |
|
1443 "fb_link" : "http://www.facebook.com/share.php?u=", |
|
1444 "tw_link" : "http://twitter.com/home?status=", |
|
1445 "gplus_link" : "" |
|
1446 }, |
|
1447 "TweetsWidget" : { |
|
1448 default_profile_picture : "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", |
|
1449 tweet_display_period: 10000 // how long do we show a tweet ? |
|
1450 }, |
|
1451 "SliderWidget" : { |
|
1452 minimize_period: 850 // how long does the slider stays maximized after the user leaves the zone ? |
|
1453 }, |
|
1454 "Main" : { |
|
1455 autoplay: true |
|
1456 } |
|
1457 |
|
1458 }; |
|
1459 |
|
1460 IriSP.paths = { |
|
1461 // "imgs": "/tweetlive/res/metadataplayer/src/css/imgs" |
|
1462 "imgs": "/mdp/src/css/imgs" |
|
1463 }; |
|
1464 IriSP.default_templates_vars = { |
|
1465 "img_dir" : IriSP.paths.imgs |
|
1466 }; |
|
1467 |
|
1468 /* ui.js - ui related functions */ |
|
1469 |
|
1470 /* FIXME: use an sharing library */ |
|
1471 IriSP.LdtShareTool = IriSP.share_template; /* the contents come from share.html */ |
|
1472 |
|
1473 IriSP.createPlayerChrome = function(){ |
|
1474 var width = IriSP.config.gui.width; |
|
1475 var height = IriSP.config.gui.height; |
|
1476 var heightS = IriSP.config.gui.height-20; |
|
1477 |
|
1478 // AUDIO */ |
|
1479 // PB dans le html : ; |
|
1480 IriSP.trace( "__IriSP.createMyHtml",IriSP.config.gui.container ); |
|
1481 |
|
1482 |
|
1483 /* FIXME : factor this in another file */ |
|
1484 if( IriSP.config.gui.mode=="radio" ){ |
|
1485 |
|
1486 IriSP.jQuery( "#"+IriSP.config.gui.container ).before(IriSP.search_template); |
|
1487 var radioPlayer = Mustache.to_html(IriSP.radio_template, {"share_template" : IriSP.share_template}); |
|
1488 IriSP.jQuery(radioPlayer).appendTo("#"+IriSP.config.gui.container); |
|
1489 |
|
1490 // special tricks for IE 7 |
|
1491 if (IriSP.jQuery.browser.msie==true && IriSP.jQuery.browser.version=="7.0"){ |
|
1492 //LdtSearchContainer |
|
1493 //__IriSP.jQuery("#LdtPlayer").attr("margin-top","50px"); |
|
1494 IriSP.jQuery("#Ldt-Root").css("padding-top","25px"); |
|
1495 IriSP.trace("__IriSP.createHtml","IE7 SPECIAL "); |
|
1496 } |
|
1497 } else if(IriSP.config.gui.mode=="video") { |
|
1498 |
|
1499 var videoPlayer = Mustache.to_html(IriSP.video_template, {"share_template" : IriSP.share_template, "heightS" : heightS}); |
|
1500 IriSP.jQuery(videoPlayer).appendTo("#"+IriSP.config.gui.container); |
|
1501 } |
|
1502 |
|
1503 IriSP.jQuery("#Ldt-Annotations").width(width-(75*2)); |
|
1504 IriSP.jQuery("#Ldt-Show-Arrow-container").width(width-(75*2)); |
|
1505 IriSP.jQuery("#Ldt-ShowAnnotation-audio").width(width-10); |
|
1506 IriSP.jQuery("#Ldt-ShowAnnotation-video").width(width-10); |
|
1507 IriSP.jQuery("#Ldt-SaKeyword").width(width-10); |
|
1508 IriSP.jQuery("#Ldt-controler").width(width-10); |
|
1509 IriSP.jQuery("#Ldt-Control").attr("z-index","100"); |
|
1510 IriSP.jQuery("#Ldt-controler").hide(); |
|
1511 |
|
1512 IriSP.jQuery(IriSP.annotation_loading_template).appendTo("#Ldt-ShowAnnotation-audio"); |
|
1513 |
|
1514 if(IriSP.config.gui.mode=='radio'){ |
|
1515 IriSP.jQuery("#Ldt-load-container").attr("width",IriSP.config.gui.width); |
|
1516 } |
|
1517 // Show or not the output |
|
1518 if(IriSP.config.gui.debug===true){ |
|
1519 IriSP.jQuery("#Ldt-output").show(); |
|
1520 } else { |
|
1521 IriSP.jQuery("#Ldt-output").hide(); |
|
1522 } |
|
1523 |
|
1524 }; |
|
1525 |
|
1526 |
|
1527 /* create the buttons and the slider */ |
|
1528 IriSP.createInterface = function( width, height, duration ) { |
|
1529 |
|
1530 IriSP.jQuery( "#Ldt-controler" ).show(); |
|
1531 //__IriSP.jQuery("#Ldt-Root").css('display','visible'); |
|
1532 IriSP.trace( "__IriSP.createInterface" , width+","+height+","+duration+"," ); |
|
1533 |
|
1534 IriSP.jQuery( "#Ldt-ShowAnnotation").click( function () { |
|
1535 //__IriSP.jQuery(this).slideUp(); |
|
1536 } ); |
|
1537 |
|
1538 var LdtpPlayerY = IriSP.jQuery("#Ldt-PlaceHolder").attr("top"); |
|
1539 var LdtpPlayerX = IriSP.jQuery("#Ldt-PlaceHolder").attr("left"); |
|
1540 |
|
1541 IriSP.jQuery( "#slider-range-min" ).slider( { //range: "min", |
|
1542 value: 0, |
|
1543 min: 1, |
|
1544 max: duration/1000,//1:54:52.66 = 3600+3240+ |
|
1545 step: 0.1, |
|
1546 slide: function(event, ui) { |
|
1547 |
|
1548 //__IriSP.jQuery("#amount").val(ui.value+" s"); |
|
1549 //player.sendEvent('SEEK', ui.value) |
|
1550 IriSP.MyApiPlayer.seek(ui.value); |
|
1551 //changePageUrlOffset(ui.value); |
|
1552 //player.sendEvent('PAUSE') |
|
1553 } |
|
1554 } ); |
|
1555 |
|
1556 IriSP.trace("__IriSP.createInterface","ICI"); |
|
1557 IriSP.jQuery("#amount").val(IriSP.jQuery("#slider-range-min").slider("value")+" s"); |
|
1558 IriSP.jQuery(".Ldt-Control1 button:first").button({ |
|
1559 icons: { |
|
1560 primary: 'ui-icon-play' |
|
1561 }, |
|
1562 text: false |
|
1563 }).next().button({ |
|
1564 icons: { |
|
1565 primary: 'ui-icon-seek-next' |
|
1566 }, |
|
1567 text: false |
|
1568 }); |
|
1569 IriSP.jQuery(".Ldt-Control2 button:first").button({ |
|
1570 icons: { |
|
1571 primary: 'ui-icon-search'//, |
|
1572 //secondary: 'ui-icon-volume-off' |
|
1573 }, |
|
1574 text: false |
|
1575 }).next().button({ |
|
1576 icons: { |
|
1577 primary: 'ui-icon-volume-on' |
|
1578 }, |
|
1579 text: false |
|
1580 }); |
|
1581 |
|
1582 // /!\ PB A MODIFIER |
|
1583 //__IriSP.MyTags.draw(); |
|
1584 IriSP.trace("__IriSP.createInterface","ICI2"); |
|
1585 IriSP.jQuery( "#ldt-CtrlPlay" ).attr( "style", "background-color:#CD21C24;" ); |
|
1586 |
|
1587 IriSP.jQuery( "#Ldt-load-container" ).hide(); |
|
1588 |
|
1589 if( IriSP.config.gui.mode=="radio" & IriSP.jQuery.browser.msie != true ) { |
|
1590 IriSP.jQuery( "#Ldtplayer1" ).attr( "height", "0" ); |
|
1591 } |
|
1592 IriSP.trace( "__IriSP.createInterface" , "3" ); |
|
1593 |
|
1594 IriSP.trace( "__IriSP.createInterface", "END" ); |
|
1595 |
|
1596 }; |
|
1597 /* the widget classes and definitions */ |
|
1598 |
|
1599 IriSP.Widget = function(Popcorn, config, Serializer) { |
|
1600 |
|
1601 if (config === undefined || config === null) { |
|
1602 config = {} |
|
1603 } |
|
1604 |
|
1605 this._Popcorn = Popcorn; |
|
1606 this._config = config; |
|
1607 this._serializer = Serializer; |
|
1608 |
|
1609 if (config.hasOwnProperty("container")) { |
|
1610 this._id = config.container; |
|
1611 this.selector = IriSP.jQuery("#" + this._id); |
|
1612 } |
|
1613 |
|
1614 if (config.hasOwnProperty("spacer")) { |
|
1615 this._spacerId = config.spacer; |
|
1616 this.spacer = IriSP.jQuery("#" + this._spacerId); |
|
1617 } |
|
1618 |
|
1619 |
|
1620 if (config.hasOwnProperty("width")) { |
|
1621 // this.width and not this._width because we consider it public. |
|
1622 this.width = config.width; |
|
1623 } |
|
1624 |
|
1625 if (config.hasOwnProperty("height")) { |
|
1626 this.height = config.height; |
|
1627 } |
|
1628 |
|
1629 if (config.hasOwnProperty("heightmax")) { |
|
1630 this.heightmax = config.heightmax; |
|
1631 } |
|
1632 |
|
1633 if (config.hasOwnProperty("widthmax")) { |
|
1634 this.widthmax = config.widthmax; |
|
1635 } |
|
1636 |
|
1637 }; |
|
1638 |
|
1639 IriSP.Widget.prototype.draw = function() { |
|
1640 /* implemented by "sub-classes" */ |
|
1641 }; |
|
1642 |
|
1643 IriSP.Widget.prototype.redraw = function() { |
|
1644 /* implemented by "sub-classes" */ |
|
1645 }; |
|
1646 /* modules are non-graphical entities, similar to widgets */ |
|
1647 |
|
1648 IriSP.Module = function(Popcorn, config, Serializer) { |
|
1649 |
|
1650 if (config === undefined || config === null) { |
|
1651 config = {} |
|
1652 } |
|
1653 |
|
1654 this._Popcorn = Popcorn; |
|
1655 this._config = config; |
|
1656 this._serializer = Serializer; |
|
1657 }; |
|
1658 /* layout.js - very basic layout management */ |
|
1659 |
|
1660 /* |
|
1661 a layout manager manages a div and the layout of objects |
|
1662 inside it. |
|
1663 */ |
|
1664 |
|
1665 IriSP.LayoutManager = function(options) { |
|
1666 this._Popcorn = null; |
|
1667 this._widgets = []; |
|
1668 |
|
1669 this._div = "LdtPlayer"; |
|
1670 this._width = 640; |
|
1671 |
|
1672 if (options === undefined) { |
|
1673 options = {}; |
|
1674 }; |
|
1675 |
|
1676 if (options.hasOwnProperty('container')) { |
|
1677 this._div = options.container; |
|
1678 } |
|
1679 |
|
1680 if (options.hasOwnProperty('width')) { |
|
1681 this._width = options.width; |
|
1682 } |
|
1683 |
|
1684 if (options.hasOwnProperty('height')) { |
|
1685 this._height = options.height; |
|
1686 } |
|
1687 |
|
1688 /* this is a shortcut */ |
|
1689 this.selector = IriSP.jQuery("#" + this._div); |
|
1690 |
|
1691 this.selector.css("width", this._width); |
|
1692 |
|
1693 if (this._height !== undefined) |
|
1694 this.selector.css("height", this._height); |
|
1695 }; |
|
1696 |
|
1697 /* we need this special setter because of a chicken and egg problem : |
|
1698 we want the manager to use popcorn but the popcorn div will be managed |
|
1699 by the manager. So we need a way to set the instance the manager uses |
|
1700 */ |
|
1701 |
|
1702 IriSP.LayoutManager.prototype.setPopcornInstance = function(popcorn) { |
|
1703 this._Popcorn = popcorn; |
|
1704 } |
|
1705 |
|
1706 /* stem is a string to append to the id of the widget */ |
|
1707 IriSP.LayoutManager.prototype.createDiv = function(stem) { |
|
1708 if (typeof(stem) === "undefined") |
|
1709 stem = ""; |
|
1710 |
|
1711 var newDiv = IriSP.guid(this._div + "_widget_" + stem + "_"); |
|
1712 var spacerDiv = IriSP.guid("LdtPlayer_spacer_"); |
|
1713 this._widgets.push(newDiv); |
|
1714 |
|
1715 var divTempl = "<div id='{{id}}' style='width: {{width}}px; position: relative;'></div"; |
|
1716 var spacerTempl = "<div id='{{spacer_id}}' style='width: {{width}}px; position: relative; height: {{spacer_div_height}};'></div"; |
|
1717 |
|
1718 var divCode = Mustache.to_html(divTempl, {id: newDiv, width: this._width}); |
|
1719 var spacerCode = Mustache.to_html(spacerTempl, {spacer_id: spacerDiv, width: this._width, |
|
1720 spacer_div_height: IriSP.widgetsDefaults.LayoutManager.spacer_div_height }); |
|
1721 |
|
1722 this.selector.append(divCode); |
|
1723 this.selector.append(spacerCode); |
|
1724 |
|
1725 return [newDiv, spacerDiv]; |
|
1726 }; |
|
1727 /* init.js - initialization and configuration of Popcorn and the widgets |
|
1728 exemple json configuration: |
|
1729 |
|
1730 */ |
|
1731 |
|
1732 IriSP.setupDataLoader = function() { |
|
1733 /* we set it up separately because we need to |
|
1734 get data at the very beginning, for instance when |
|
1735 setting up the video */ |
|
1736 IriSP.__dataloader = new IriSP.DataLoader(); |
|
1737 }; |
|
1738 |
|
1739 IriSP.configurePopcorn = function (layoutManager, options) { |
|
1740 var pop; |
|
1741 var ret = layoutManager.createDiv(); |
|
1742 var containerDiv = ret[0]; |
|
1743 |
|
1744 switch(options.type) { |
|
1745 /* |
|
1746 todo : dynamically create the div/video tag which |
|
1747 will contain the video. |
|
1748 */ |
|
1749 case "html5": |
|
1750 var tmpId = Popcorn.guid("video"); |
|
1751 IriSP.jQuery("#" + containerDiv).append("<video src='" + options.file + "' id='" + tmpId + "'></video>"); |
|
1752 |
|
1753 if (options.hasOwnProperty("width")) |
|
1754 IriSP.jQuery("#" + containerDiv).css("width", options.width); |
|
1755 |
|
1756 if (options.hasOwnProperty("height")) |
|
1757 IriSP.jQuery("#" + containerDiv).css("height", options.height); |
|
1758 |
|
1759 pop = Popcorn("#" + tmpId); |
|
1760 break; |
|
1761 |
|
1762 case "jwplayer": |
|
1763 var opts = IriSP.jQuery.extend({}, options); |
|
1764 delete opts.container; |
|
1765 |
|
1766 if (options.provider === "rtmp") { |
|
1767 /* exit if we can't access the metadata */ |
|
1768 if (typeof(IriSP.__jsonMetadata) === "undefined") { |
|
1769 break; |
|
1770 }; |
|
1771 |
|
1772 |
|
1773 // the json format is totally illogical |
|
1774 opts.streamer = IriSP.__jsonMetadata["medias"][0]["meta"]["item"]["value"]; |
|
1775 var source = IriSP.__jsonMetadata["medias"][0]["href"]; |
|
1776 |
|
1777 // the source if a full url but jwplayer wants an url relative to the |
|
1778 // streamer url, so we've got to remove the common part. |
|
1779 opts.file = source.slice(opts.streamer.length); |
|
1780 } else { |
|
1781 /* other providers type, video for instance - |
|
1782 pass everything as is */ |
|
1783 } |
|
1784 |
|
1785 pop = IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts); |
|
1786 break; |
|
1787 |
|
1788 case "youtube": |
|
1789 var opts = IriSP.jQuery.extend({}, options); |
|
1790 delete opts.container; |
|
1791 opts.controls = 0; |
|
1792 opts.autostart = false; |
|
1793 templ = "width: {{width}}px; height: {{height}}px;"; |
|
1794 var str = Mustache.to_html(templ, {width: opts.width, height: opts.height}); |
|
1795 // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div. |
|
1796 IriSP.jQuery("#" + containerDiv).attr("style", str); |
|
1797 |
|
1798 pop = Popcorn.youtube("#" + containerDiv, opts.video, opts); |
|
1799 break; |
|
1800 |
|
1801 default: |
|
1802 pop = undefined; |
|
1803 }; |
|
1804 |
|
1805 return pop; |
|
1806 }; |
|
1807 |
|
1808 IriSP.configureWidgets = function (popcornInstance, layoutManager, guiOptions) { |
|
1809 |
|
1810 var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader); |
|
1811 var params = {width: guiOptions.width, height: guiOptions.height}; |
|
1812 |
|
1813 var ret_widgets = []; |
|
1814 var index; |
|
1815 |
|
1816 for (index = 0; index < guiOptions.widgets.length; index++) { |
|
1817 var widgetConfig = guiOptions.widgets[index]; |
|
1818 var widget = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, widgetConfig); |
|
1819 ret_widgets.push(widget); |
|
1820 |
|
1821 }; |
|
1822 |
|
1823 return ret_widgets; |
|
1824 }; |
|
1825 |
|
1826 IriSP.configureModules = function (popcornInstance, modulesList) { |
|
1827 |
|
1828 var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader); |
|
1829 var ret_modules = []; |
|
1830 var index; |
|
1831 |
|
1832 for (index = 0; index < modulesList.length; index++) { |
|
1833 var moduleConfig = modulesList[index]; |
|
1834 |
|
1835 var serializer = serialFactory.getSerializer(moduleConfig.metadata); |
|
1836 var module = new IriSP[moduleConfig.type](popcornInstance, moduleConfig, serializer); |
|
1837 ret_modules.push(module); |
|
1838 }; |
|
1839 |
|
1840 return ret_modules; |
|
1841 }; |
|
1842 |
|
1843 IriSP.instantiateWidget = function(popcornInstance, serialFactory, layoutManager, widgetConfig) { |
|
1844 /* create div returns us a container for the widget and a spacer */ |
|
1845 var ret = layoutManager.createDiv(widgetConfig.type); |
|
1846 var container = ret[0]; |
|
1847 var spacer = ret[1]; |
|
1848 |
|
1849 var arr = IriSP.jQuery.extend({}, widgetConfig); |
|
1850 arr.container = container; |
|
1851 arr.spacer = spacer; |
|
1852 |
|
1853 var serializer = serialFactory.getSerializer(widgetConfig.metadata); |
|
1854 |
|
1855 if (typeof serializer == "undefined") |
|
1856 debugger; |
|
1857 |
|
1858 // instantiate the object passed as a string |
|
1859 var widget = new IriSP[widgetConfig.type](popcornInstance, arr, serializer); |
|
1860 |
|
1861 if (widgetConfig.hasOwnProperty("requires")) { |
|
1862 // also create the widgets this one depends on. |
|
1863 // the dependency widget is available in the parent widget context as |
|
1864 // this.WidgetName (for instance, this.TipWidget); |
|
1865 |
|
1866 var i = 0; |
|
1867 for(i = 0; i < widgetConfig.requires.length; i++) { |
|
1868 var widgetName = widgetConfig.requires[i]["type"]; |
|
1869 widget[widgetName] = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, widgetConfig.requires[i]); |
|
1870 } |
|
1871 } |
|
1872 |
|
1873 serializer.sync(IriSP.wrap(widget, function() { this.draw(); })); |
|
1874 return widget; |
|
1875 }; |
|
1876 /* mediafragment module */ |
|
1877 |
|
1878 IriSP.MediaFragment = function(Popcorn, config, Serializer) { |
|
1879 IriSP.Module.call(this, Popcorn, config, Serializer); |
|
1880 |
|
1881 this.mutex = false; /* a mutex because we access the url from two different functions */ |
|
1882 |
|
1883 this._Popcorn.listen( "loadedmetadata", IriSP.wrap(this, IriSP.MediaFragment.advanceTime)); |
|
1884 this._Popcorn.listen( "pause", IriSP.wrap(this, IriSP.MediaFragment.updateTime)); |
|
1885 this._Popcorn.listen( "seeked", IriSP.wrap(this, IriSP.MediaFragment.updateTime)); |
|
1886 this._Popcorn.listen( "IriSP.PolemicTweet.click", IriSP.wrap(this, IriSP.MediaFragment.updateAnnotation)); |
|
1887 this._Popcorn.listen( "IriSP.SegmentsWidget.click", IriSP.wrap(this, IriSP.MediaFragment.updateAnnotation)); |
|
1888 }; |
|
1889 |
|
1890 IriSP.MediaFragment.advanceTime = function() { |
|
1891 var url = window.location.href; |
|
1892 |
|
1893 if ( url.split( "#" )[ 1 ] != null ) { |
|
1894 pageoffset = url.split( "#" )[1]; |
|
1895 |
|
1896 if ( pageoffset.substring(0, 2) === "t=") { |
|
1897 // timecode |
|
1898 if ( pageoffset.substring( 2 ) != null ) { |
|
1899 var offsettime = pageoffset.substring( 2 ); |
|
1900 this._Popcorn.currentTime( parseFloat( offsettime ) ); |
|
1901 } |
|
1902 } else if ( pageoffset.substring(0, 2) === "a=") { |
|
1903 // annotation |
|
1904 var annotationId = pageoffset.substring( 2 ); |
|
1905 |
|
1906 // there's no better way than that because |
|
1907 // of possible race conditions |
|
1908 this._serializer.sync(IriSP.wrap(this, function() { |
|
1909 IriSP.MediaFragment.lookupAnnotation.call(this, annotationId); |
|
1910 })); |
|
1911 } |
|
1912 } |
|
1913 }; |
|
1914 |
|
1915 IriSP.MediaFragment.updateTime = function() { |
|
1916 if (this.mutex === true) { |
|
1917 return; |
|
1918 } |
|
1919 |
|
1920 var history = window.history; |
|
1921 if ( !history.pushState ) { |
|
1922 return false; |
|
1923 } |
|
1924 |
|
1925 splitArr = window.location.href.split( "#" ) |
|
1926 history.replaceState( {}, "", splitArr[0] + "#t=" + this._Popcorn.currentTime().toFixed( 2 ) ); |
|
1927 }; |
|
1928 |
|
1929 |
|
1930 IriSP.MediaFragment.updateAnnotation = function(annotationId) { |
|
1931 var _this = this; |
|
1932 this.mutex = true; |
|
1933 |
|
1934 var history = window.history; |
|
1935 if ( !history.pushState ) { |
|
1936 return false; |
|
1937 } |
|
1938 |
|
1939 splitArr = window.location.href.split( "#" ) |
|
1940 history.replaceState( {}, "", splitArr[0] + "#a=" + annotationId); |
|
1941 |
|
1942 window.setTimeout(function() { _this.mutex = false }, 50); |
|
1943 }; |
|
1944 |
|
1945 // lookup and seek to the beginning of an annotation |
|
1946 IriSP.MediaFragment.lookupAnnotation = function(annotationId) { |
|
1947 var annotation = undefined; |
|
1948 var annotations = this._serializer._data.annotations; |
|
1949 |
|
1950 var i; |
|
1951 for (i = 0; i < annotations.length; i++) { |
|
1952 if (annotations[i].id === annotationId) { |
|
1953 annotation = annotations[i]; |
|
1954 break; |
|
1955 } |
|
1956 } |
|
1957 |
|
1958 if (typeof(annotation) !== "undefined") { |
|
1959 this._Popcorn.currentTime(annotation.begin / 1000); |
|
1960 } |
|
1961 }; |
|
1962 IriSP.AnnotationsWidget = function(Popcorn, config, Serializer) { |
|
1963 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
1964 |
|
1965 }; |
|
1966 |
|
1967 |
|
1968 IriSP.AnnotationsWidget.prototype = new IriSP.Widget(); |
|
1969 |
|
1970 IriSP.AnnotationsWidget.prototype.clear = function() { |
|
1971 this.selector.find(".Ldt-SaTitle").text(""); |
|
1972 this.selector.find(".Ldt-SaDescription").text(""); |
|
1973 this.selector.find(".Ldt-SaKeywordText").text(""); |
|
1974 }; |
|
1975 |
|
1976 IriSP.AnnotationsWidget.prototype.displayAnnotation = function(annotation) { |
|
1977 |
|
1978 var title = annotation.content.title; |
|
1979 var description = annotation.content.description; |
|
1980 var keywords = "" // FIXME; |
|
1981 var begin = +annotation.begin / 1000; |
|
1982 var end = +annotation.end / 1000; |
|
1983 var duration = +this._serializer.currentMedia().meta["dc:duration"]; |
|
1984 |
|
1985 var title_templ = "{{title}} - ( {{begin}} - {{end}} )"; |
|
1986 var endstr = Mustache.to_html(title_templ, {title: title, begin: IriSP.secondsToTime(begin), end: IriSP.secondsToTime(end)}); |
|
1987 |
|
1988 this.selector.find(".Ldt-SaTitle").text(endstr); |
|
1989 this.selector.find(".Ldt-SaDescription").text(description); |
|
1990 |
|
1991 // update sharing buttons |
|
1992 var defaults = IriSP.widgetsDefaults.AnnotationsWidget; |
|
1993 var text = defaults.share_text; |
|
1994 var fb_link = defaults.fb_link; |
|
1995 var tw_link = defaults.tw_link; |
|
1996 var gplus_link = defaults.gplus_link; |
|
1997 var url = document.location.href + "#a=" + annotation.id; |
|
1998 this.selector.find(".Ldt-fbShare").attr("href", fb_link + IriSP.encodeURI(text) + IriSP.encodeURI(url)); |
|
1999 this.selector.find(".Ldt-TwShare").attr("href", tw_link + IriSP.encodeURI(text) + IriSP.encodeURI(url)); |
|
2000 this.selector.find(".Ldt-GplusShare").attr("href", fb_link + IriSP.encodeURI(text) + IriSP.encodeURI(url)); |
|
2001 }; |
|
2002 |
|
2003 IriSP.AnnotationsWidget.prototype.clearWidget = function() { |
|
2004 |
|
2005 |
|
2006 /* retract the pane between two annotations */ |
|
2007 this.selector.find(".Ldt-SaTitle").text(""); |
|
2008 this.selector.find(".Ldt-SaDescription").text(""); |
|
2009 this.selector.find(".Ldt-SaKeywordText").html(""); |
|
2010 this.selector.find(".Ldt-ShowAnnotation").slideUp(); |
|
2011 }; |
|
2012 |
|
2013 IriSP.AnnotationsWidget.prototype.draw = function() { |
|
2014 var _this = this; |
|
2015 |
|
2016 var annotationMarkup = IriSP.templToHTML(IriSP.annotationWidget_template); |
|
2017 this.selector.append(annotationMarkup); |
|
2018 var view; |
|
2019 |
|
2020 if (typeof(this._serializer._data.views) !== "undefined" && this._serializer._data.views !== null) |
|
2021 view = this._serializer._data.views[0]; |
|
2022 |
|
2023 var view_type = ""; |
|
2024 |
|
2025 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
2026 view_type = view.annotation_types[0]; |
|
2027 } |
|
2028 |
|
2029 var annotations = this._serializer._data.annotations; |
|
2030 var i; |
|
2031 |
|
2032 for (i in annotations) { |
|
2033 var annotation = annotations[i]; |
|
2034 var begin = Math.round((+ annotation.begin) / 1000); |
|
2035 var end = Math.round((+ annotation.end) / 1000); |
|
2036 |
|
2037 if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" |
|
2038 && annotation.meta["id-ref"] != view_type) { |
|
2039 continue; |
|
2040 } |
|
2041 |
|
2042 |
|
2043 var conf = {start: begin, end: end, |
|
2044 onStart: |
|
2045 function(annotation) { |
|
2046 return function() { |
|
2047 _this.displayAnnotation(annotation); |
|
2048 |
|
2049 } }(annotation), |
|
2050 onEnd: |
|
2051 function() { _this.clearWidget.call(_this); } |
|
2052 }; |
|
2053 this._Popcorn = this._Popcorn.code(conf); |
|
2054 } |
|
2055 |
|
2056 }; |
|
2057 IriSP.ArrowWidget = function(Popcorn, config, Serializer) { |
|
2058 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2059 |
|
2060 this._oldAnnotation = null; |
|
2061 |
|
2062 }; |
|
2063 |
|
2064 |
|
2065 IriSP.ArrowWidget.prototype = new IriSP.Widget(); |
|
2066 |
|
2067 IriSP.ArrowWidget.prototype.clear = function() { |
|
2068 |
|
2069 }; |
|
2070 |
|
2071 IriSP.ArrowWidget.prototype.clearWidget = function() { |
|
2072 }; |
|
2073 |
|
2074 IriSP.ArrowWidget.prototype.draw = function() { |
|
2075 var templ = Mustache.to_html(IriSP.arrowWidget_template, {}); |
|
2076 this.selector.append(templ); |
|
2077 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler)); |
|
2078 }; |
|
2079 |
|
2080 IriSP.ArrowWidget.prototype.timeUpdateHandler = function(percents) { |
|
2081 var currentTime = this._Popcorn.currentTime(); |
|
2082 var currentAnnotation = this._serializer.currentAnnotations(currentTime)[0]; // FIXME : use the others ? |
|
2083 |
|
2084 /* move the arrow only if the current annotation changes */ |
|
2085 if (currentAnnotation != this._oldAnnotation) { |
|
2086 var begin = (+ currentAnnotation.begin) / 1000; |
|
2087 var end = (+ currentAnnotation.end) / 1000; |
|
2088 |
|
2089 var duration = +this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
2090 var middle_time = (begin + end) / 2; |
|
2091 var percents = Math.floor((middle_time / duration) * 100); |
|
2092 |
|
2093 // we need to apply a fix because the arrow has a certain length |
|
2094 // it's half the length of the arrow (27 / 2). We need to convert |
|
2095 // it in percents though. |
|
2096 var totalWidth = this.selector.width(); |
|
2097 var correction = ((27 / 2) / totalWidth) * 100; |
|
2098 var corrected_percents = percents - correction; |
|
2099 |
|
2100 /* don't move out of the screen */ |
|
2101 if (corrected_percents <= 0) |
|
2102 corrected_percents = 0; |
|
2103 |
|
2104 this.selector.children(".Ldt-arrowWidget").animate({"left" : corrected_percents + "%"}); |
|
2105 |
|
2106 this._oldAnnotation = currentAnnotation; |
|
2107 } |
|
2108 } |
|
2109 IriSP.PlayerWidget = function(Popcorn, config, Serializer) { |
|
2110 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2111 |
|
2112 this._searchBlockOpen = false; |
|
2113 this._searchLastValue = ""; |
|
2114 }; |
|
2115 |
|
2116 IriSP.PlayerWidget.prototype = new IriSP.Widget(); |
|
2117 |
|
2118 IriSP.PlayerWidget.prototype.draw = function() { |
|
2119 var self = this; |
|
2120 var width = this.width; |
|
2121 var height = this.height; |
|
2122 var heightS = this.height-20; |
|
2123 |
|
2124 var Player_templ = Mustache.to_html(IriSP.player_template, {"share_template" : IriSP.share_template}); |
|
2125 this.selector.append(Player_templ); |
|
2126 |
|
2127 this.selector.children(".Ldt-controler").show(); |
|
2128 |
|
2129 // handle clicks by the user on the video. |
|
2130 this._Popcorn.listen("play", IriSP.wrap(this, this.playButtonUpdater)); |
|
2131 this._Popcorn.listen("pause", IriSP.wrap(this, this.playButtonUpdater)); |
|
2132 |
|
2133 this._Popcorn.listen("volumechange", IriSP.wrap(this, this.muteButtonUpdater)); |
|
2134 |
|
2135 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeDisplayUpdater)); |
|
2136 this._Popcorn.listen("IriSP.search.matchFound", IriSP.wrap(this, this.searchMatch)); |
|
2137 this._Popcorn.listen("IriSP.search.noMatchFound", IriSP.wrap(this, this.searchNoMatch)); |
|
2138 |
|
2139 |
|
2140 this.selector.find(".Ldt-CtrlPlay").click(function() { self.playHandler.call(self); }); |
|
2141 this.selector.find(".Ldt-CtrlNext").click(function() { }); |
|
2142 this.selector.find(".Ldt-CtrlSearch").click(function() { self.searchButtonHandler.call(self); }); |
|
2143 |
|
2144 this.selector.find('.Ldt-CtrlSound').click(function() { self.muteHandler.call(self); } ); |
|
2145 |
|
2146 this.selector.find(".Ldt-CtrlPlay").attr( "style", "background-color:#CD21C24;" ); |
|
2147 |
|
2148 var searchButtonPos = this.selector.find(".Ldt-CtrlSearch").position(); |
|
2149 var searchBox = Mustache.to_html(IriSP.search_template, {margin_left : searchButtonPos.left + "px"}); |
|
2150 this.selector.append(searchBox); |
|
2151 |
|
2152 // trigger an IriSP.PlayerWidget.MouseOver to the widgets that are interested (i.e : sliderWidget) |
|
2153 this.selector.hover(function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOver"); }, |
|
2154 function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOut"); }); |
|
2155 |
|
2156 this.muteButtonUpdater(); /* some player - jwplayer notable - save the state of the mute button between sessions */ |
|
2157 }; |
|
2158 |
|
2159 /* Update the elasped time div */ |
|
2160 IriSP.PlayerWidget.prototype.timeDisplayUpdater = function() { |
|
2161 |
|
2162 if (this._previousSecond === undefined) |
|
2163 this._previousSecond = this._Popcorn.roundTime(); |
|
2164 |
|
2165 else { |
|
2166 /* we're still in the same second, so it's not necessary to update time */ |
|
2167 if (this._Popcorn.roundTime() == this._previousSecond) |
|
2168 return; |
|
2169 |
|
2170 } |
|
2171 |
|
2172 // we get it at each call because it may change. |
|
2173 var duration = +this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
2174 var totalTime = IriSP.secondsToTime(duration); |
|
2175 var elapsedTime = IriSP.secondsToTime(this._Popcorn.currentTime()); |
|
2176 |
|
2177 this.selector.find(".Ldt-ElapsedTime").html(elapsedTime.toString()); |
|
2178 this.selector.find(".Ldt-TotalTime").html(totalTime.toString()); |
|
2179 this._previousSecond = this._Popcorn.roundTime(); |
|
2180 }; |
|
2181 |
|
2182 /* update the icon of the button - separate function from playHandler |
|
2183 because in some cases (for instance, when the user directly clicks on |
|
2184 the jwplayer window) we have to change the icon without playing/pausing |
|
2185 */ |
|
2186 IriSP.PlayerWidget.prototype.playButtonUpdater = function() { |
|
2187 var status = this._Popcorn.media.paused; |
|
2188 |
|
2189 if ( status == true ){ |
|
2190 this.selector.find(".Ldt-CtrlPlay").attr("title", "Play"); |
|
2191 |
|
2192 // we use templToHTML because it has some predefined |
|
2193 // vars like where to get the images |
|
2194 var templ = IriSP.templToHTML("url({{img_dir}}/play_sprite.png)"); |
|
2195 this.selector.find(".Ldt-CtrlPlay").css("background-image", templ); |
|
2196 |
|
2197 } else { |
|
2198 this.selector.find(".Ldt-CtrlPlay").attr("title", "Pause"); |
|
2199 |
|
2200 // we use templToHTML because it has some predefined |
|
2201 // vars like where to get the images |
|
2202 var templ = IriSP.templToHTML("url({{img_dir}}/pause_sprite.png)"); |
|
2203 this.selector.find(".Ldt-CtrlPlay").css("background-image", templ); |
|
2204 } |
|
2205 |
|
2206 return; |
|
2207 }; |
|
2208 |
|
2209 |
|
2210 IriSP.PlayerWidget.prototype.playHandler = function() { |
|
2211 var status = this._Popcorn.media.paused; |
|
2212 |
|
2213 if ( status == true ){ |
|
2214 this._Popcorn.play(); |
|
2215 } else { |
|
2216 this._Popcorn.pause(); |
|
2217 } |
|
2218 }; |
|
2219 |
|
2220 IriSP.PlayerWidget.prototype.muteHandler = function() { |
|
2221 if (!this._Popcorn.muted()) { |
|
2222 this._Popcorn.mute(true); |
|
2223 } else { |
|
2224 this._Popcorn.mute(false); |
|
2225 } |
|
2226 }; |
|
2227 |
|
2228 IriSP.PlayerWidget.prototype.muteButtonUpdater = function() { |
|
2229 var status = this._Popcorn.media.muted; |
|
2230 |
|
2231 if ( status == true ){ |
|
2232 this.selector.find(".Ldt-CtrlSound").attr("title", "Unmute"); |
|
2233 |
|
2234 // we use templToHTML because it has some predefined |
|
2235 // vars like where to get the images |
|
2236 var templ = IriSP.templToHTML("url({{img_dir}}/sound_sprite.png)"); |
|
2237 this.selector.find(".Ldt-CtrlSound").css("background-image", templ); |
|
2238 |
|
2239 } else { |
|
2240 this.selector.find(".Ldt-CtrlSound").attr("title", "Mute"); |
|
2241 |
|
2242 // we use templToHTML because it has some predefined |
|
2243 // vars like where to get the images |
|
2244 var templ = IriSP.templToHTML("url({{img_dir}}/mute_sprite.png)"); |
|
2245 this.selector.find(".Ldt-CtrlSound").css("background-image", templ); |
|
2246 } |
|
2247 |
|
2248 return; |
|
2249 }; |
|
2250 |
|
2251 |
|
2252 IriSP.PlayerWidget.prototype.searchButtonHandler = function() { |
|
2253 var self = this; |
|
2254 |
|
2255 /* show the search field if it is not shown */ |
|
2256 if ( this._searchBlockOpen == false ) { |
|
2257 this.selector.find(".LdtSearch").show(100); |
|
2258 |
|
2259 this.selector.find(".LdtSearchInput").css('background-color','#fff'); |
|
2260 this.selector.find(".LdtSearchInput").focus(); |
|
2261 this.selector.find(".LdtSearchInput").attr('value', this._searchLastValue); |
|
2262 this._Popcorn.trigger("IriSP.search", this._searchLastValue); // trigger the search to make it more natural. |
|
2263 |
|
2264 this._searchBlockOpen = true; |
|
2265 this.selector.find(".LdtSearchInput").bind('keyup', null, function() { self.searchHandler.call(self); } ); |
|
2266 |
|
2267 // we need this variable because some widget can find a match in |
|
2268 // their data while at the same time other's don't. As we want the |
|
2269 // search field to become green when there's a match, we need a |
|
2270 // variable to remember that we had one. |
|
2271 this._positiveMatch = false; |
|
2272 |
|
2273 // tell the world the field is open |
|
2274 this._Popcorn.trigger("IriSP.search.open"); |
|
2275 |
|
2276 } else { |
|
2277 this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); |
|
2278 this.selector.find(".LdtSearchInput").attr('value',''); |
|
2279 this.selector.find(".LdtSearch").hide(100); |
|
2280 |
|
2281 // unbind the watcher event. |
|
2282 this.selector.find(".LdtSearchInput").unbind('keypress set'); |
|
2283 this._searchBlockOpen = false; |
|
2284 |
|
2285 this._positiveMatch = false; |
|
2286 |
|
2287 this._Popcorn.trigger("IriSP.search.closed"); |
|
2288 } |
|
2289 }; |
|
2290 |
|
2291 /* this handler is called whenever the content of the search |
|
2292 field changes */ |
|
2293 IriSP.PlayerWidget.prototype.searchHandler = function() { |
|
2294 this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); |
|
2295 this._positiveMatch = false; |
|
2296 |
|
2297 // do nothing if the search field is empty, instead of highlighting everything. |
|
2298 if (this._searchLastValue == "") { |
|
2299 this._Popcorn.trigger("IriSP.search.cleared"); |
|
2300 this.selector.find(".LdtSearchInput").css('background-color',''); |
|
2301 } else { |
|
2302 this._Popcorn.trigger("IriSP.search", this._searchLastValue); |
|
2303 } |
|
2304 }; |
|
2305 |
|
2306 /* |
|
2307 handler for the IriSP.search.found message, which is sent by some views when they |
|
2308 highlight a match. |
|
2309 */ |
|
2310 IriSP.PlayerWidget.prototype.searchMatch = function() { |
|
2311 this._positiveMatch = true; |
|
2312 this.selector.find(".LdtSearchInput").css('background-color','#e1ffe1'); |
|
2313 } |
|
2314 |
|
2315 /* the same, except that no value could be found */ |
|
2316 IriSP.PlayerWidget.prototype.searchNoMatch = function() { |
|
2317 if (this._positiveMatch !== true) |
|
2318 this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a"); |
|
2319 } |
|
2320 |
|
2321 /* |
|
2322 * |
|
2323 * Copyright 2010 Institut de recherche et d'innovation |
|
2324 * contributor(s) : Samuel Huron |
|
2325 * |
|
2326 * contact@iri.centrepompidou.fr |
|
2327 * http://www.iri.centrepompidou.fr |
|
2328 * |
|
2329 * This software is a computer program whose purpose is to show and add annotations on a video . |
|
2330 * This software is governed by the CeCILL-C license under French law and |
|
2331 * abiding by the rules of distribution of free software. You can use, |
|
2332 * modify and/ or redistribute the software under the terms of the CeCILL-C |
|
2333 * license as circulated by CEA, CNRS and INRIA at the following URL |
|
2334 * "http://www.cecill.info". |
|
2335 * |
|
2336 * The fact that you are presently reading this means that you have had |
|
2337 * knowledge of the CeCILL-C license and that you accept its terms. |
|
2338 */ |
|
2339 // CHART TIMELINE / VERSION PROTOTYPE :: |
|
2340 |
|
2341 IriSP.PolemicWidget = function(Popcorn, config, Serializer) { |
|
2342 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2343 |
|
2344 this.userPol = new Array(); |
|
2345 this.userNoPol = new Array(); |
|
2346 this.userst = new Array(); |
|
2347 this.numberOfTweet = 0; |
|
2348 this.Users; |
|
2349 this.TweetPolemic; |
|
2350 this.yMax = this.height; |
|
2351 this.PaperSlider; |
|
2352 this.heightOfChart; |
|
2353 this.tweets = new Array(); |
|
2354 this.svgElements = {}; |
|
2355 |
|
2356 // Make and define the Raphael area |
|
2357 this.paper = Raphael(document.getElementById(this._id), config.width, config.height); |
|
2358 |
|
2359 this.oldSearchMatches = []; |
|
2360 |
|
2361 // event handlers |
|
2362 this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { this.searchHandler(searchString); })); |
|
2363 this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.searchFieldClosedHandler)); |
|
2364 this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.searchFieldClearedHandler)); |
|
2365 |
|
2366 }; |
|
2367 |
|
2368 IriSP.PolemicWidget.prototype = new IriSP.Widget(); |
|
2369 |
|
2370 IriSP.PolemicWidget.prototype.draw = function() { |
|
2371 |
|
2372 // variable |
|
2373 // yMax |
|
2374 |
|
2375 var self = this; |
|
2376 var yCoef = 2; // coef for height of 1 tweet |
|
2377 var frameSize = 5; // frame size |
|
2378 var margin = 1; // marge between frame |
|
2379 var lineSize = this.width; // timeline pixel width |
|
2380 var nbrframes = lineSize/frameSize; // frame numbers |
|
2381 var numberOfTweet = 0; // number of tweet overide later |
|
2382 var duration = +this._serializer.currentMedia().meta["dc:duration"]; // timescale width |
|
2383 var frameLength = lineSize / frameSize; // frame timescale |
|
2384 var timeline; |
|
2385 var colors = new Array("","#1D973D","#C5A62D","#CE0A15","#036AAE","#585858"); |
|
2386 |
|
2387 // array |
|
2388 //var tweets = new Array(); |
|
2389 var element = new Array(); |
|
2390 var cluster = new Array(); |
|
2391 var frames = new Array(frameLength); |
|
2392 var slices = new Array(); |
|
2393 |
|
2394 |
|
2395 // Classes ======================================================================= |
|
2396 var Frames = function(){ |
|
2397 |
|
2398 var Myclusters; |
|
2399 var x; |
|
2400 var y; |
|
2401 var width; |
|
2402 var height; |
|
2403 }; |
|
2404 Frames = function(json){ |
|
2405 // make my clusters |
|
2406 // ou Frame vide |
|
2407 }; |
|
2408 Frames.prototype.draw = function(){ |
|
2409 }; |
|
2410 Frames.prototype.zoom = function(){ |
|
2411 }; |
|
2412 Frames.prototype.inside = function(){ |
|
2413 }; |
|
2414 var Clusters = function(){ |
|
2415 var Object; |
|
2416 var yDist; |
|
2417 var x; |
|
2418 var y; |
|
2419 var width; |
|
2420 var height; |
|
2421 }; |
|
2422 Clusters = function(json){ |
|
2423 // make my object |
|
2424 }; |
|
2425 var Tweet = function(){ |
|
2426 }; |
|
2427 // Classes ======================================================================= |
|
2428 |
|
2429 // Refactoring (parametere) ************************************************************ |
|
2430 // color translastion |
|
2431 var qTweet_0 =0; |
|
2432 var qTweet_Q =0; |
|
2433 var qTweet_REF=0; |
|
2434 var qTweet_OK =0; |
|
2435 var qTweet_KO =0; |
|
2436 function colorTranslation(value){ |
|
2437 if(value == "Q"){ |
|
2438 qTweet_Q+=1; |
|
2439 return 2; |
|
2440 }else if(value =="REF"){ |
|
2441 qTweet_REF+=1; |
|
2442 return 4; |
|
2443 }else if(value =="OK"){ |
|
2444 qTweet_OK+=1; |
|
2445 return 1; |
|
2446 }else if(value =="KO"){ |
|
2447 qTweet_KO+=1; |
|
2448 return 3; |
|
2449 }else if(value ==""){ |
|
2450 qTweet_0+=1; |
|
2451 return 5; |
|
2452 } |
|
2453 } |
|
2454 |
|
2455 |
|
2456 this._serializer.sync(function(data) { loaded_callback.call(self, data) }); |
|
2457 |
|
2458 function loaded_callback (json) { |
|
2459 |
|
2460 // get current view (the first ???) |
|
2461 view = json.views[0]; |
|
2462 |
|
2463 // the tweets are by definition of the second annotation type FIXME ? |
|
2464 tweet_annot_type = null; |
|
2465 if(typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
2466 tweet_annot_type = view.annotation_types[1]; |
|
2467 } |
|
2468 |
|
2469 for(var i = 0; i < json.annotations.length; i++) { |
|
2470 var item = json.annotations[i]; |
|
2471 var MyTime = Math.floor(item.begin/duration*lineSize); |
|
2472 var Myframe = Math.floor(MyTime/lineSize*frameLength); |
|
2473 |
|
2474 if (typeof(item.meta) !== "undefined" |
|
2475 && typeof(item.meta["id-ref"]) !== "undefined" |
|
2476 && item.meta["id-ref"] === tweet_annot_type) { |
|
2477 |
|
2478 var MyTJson = JSON.parse(item.meta['dc:source']['content']); |
|
2479 |
|
2480 if (item.content['polemics'] != undefined |
|
2481 && item.content['polemics'][0] != null) { |
|
2482 |
|
2483 // a tweet can have many polemics at the same time. |
|
2484 for(var j=0; j<item.content['polemics'].length; j++){ |
|
2485 |
|
2486 this.tweets[numberOfTweet] = { |
|
2487 id:i, |
|
2488 qualification:colorTranslation(item.content['polemics'][j]), |
|
2489 yIndicator:MyTime, |
|
2490 yframe:Myframe, |
|
2491 title:item.content['title'], |
|
2492 timeframe:item.begin, |
|
2493 userId: MyTJson.id, |
|
2494 userScreenName: MyTJson.screen_name, |
|
2495 tsource:MyTJson, |
|
2496 cinecast_id: item.id |
|
2497 }; |
|
2498 numberOfTweet+=1; |
|
2499 |
|
2500 } |
|
2501 } |
|
2502 else { |
|
2503 this.tweets[numberOfTweet] = { |
|
2504 id:i, |
|
2505 qualification:colorTranslation(""), |
|
2506 yIndicator:MyTime, |
|
2507 yframe:Myframe, |
|
2508 title:item.content['title'], |
|
2509 timeframe:item.begin, |
|
2510 userId: MyTJson.id, |
|
2511 userScreenName: MyTJson.screen_name, |
|
2512 tsource:MyTJson, |
|
2513 cinecast_id: item.id |
|
2514 }; |
|
2515 numberOfTweet+=1; |
|
2516 } |
|
2517 |
|
2518 } |
|
2519 }; |
|
2520 |
|
2521 DrawTweets.call (this); // FIXME: ugly. |
|
2522 |
|
2523 }; |
|
2524 |
|
2525 // tweet Drawing (in raphael) |
|
2526 function DrawTweets (){ |
|
2527 // GROUPES TWEET ============================================ |
|
2528 // Count nbr of cluster and tweet in a frame an save int in "frames" |
|
2529 numberOfTweet = this.tweets.length; |
|
2530 for(var i=0; i<nbrframes; i++) { |
|
2531 for(var j=0; j<numberOfTweet; j++) { |
|
2532 |
|
2533 if (i==this.tweets[j].yframe){ |
|
2534 |
|
2535 var k = this.tweets[j].qualification; |
|
2536 |
|
2537 // make array for frame cluster |
|
2538 if(frames[i]==undefined){ |
|
2539 frames[i] = {id:i, |
|
2540 qualifVol:new Array(), |
|
2541 mytweetsID:new Array() |
|
2542 }; |
|
2543 } |
|
2544 // add my tweet to frame |
|
2545 frames[i].mytweetsID.push(this.tweets[j]); |
|
2546 |
|
2547 // count opinion by frame |
|
2548 if( frames[i].qualifVol[k] == undefined){ |
|
2549 frames[i].qualifVol[k] = 1; |
|
2550 }else{ |
|
2551 frames[i].qualifVol[k] += 1; |
|
2552 } |
|
2553 |
|
2554 } |
|
2555 } |
|
2556 } |
|
2557 |
|
2558 // GROUPES TWEET ============================================ |
|
2559 // max of tweet by Frame |
|
2560 var max = 0; |
|
2561 for(var i = 0; i < nbrframes; i++) { |
|
2562 var moy = 0; |
|
2563 for (var j = 0; j < 6; j++) { |
|
2564 if (frames[i] != undefined) { |
|
2565 if (frames[i].qualifVol[j] != undefined) { |
|
2566 moy += frames[i].qualifVol[j]; |
|
2567 } |
|
2568 } |
|
2569 } |
|
2570 |
|
2571 if (moy > max) { |
|
2572 max = moy; |
|
2573 } |
|
2574 } |
|
2575 |
|
2576 var tweetDrawed = new Array(); |
|
2577 var TweetHeight = 5; |
|
2578 |
|
2579 // DRAW TWEETS ============================================ |
|
2580 for(var i = 0; i < nbrframes; i++) { |
|
2581 var addEheight = 5; |
|
2582 if (frames[i] != undefined){ |
|
2583 // by type |
|
2584 |
|
2585 for (var j = 6; j > -1; j--) { |
|
2586 if (frames[i].qualifVol[j] != undefined) { |
|
2587 // show tweet by type |
|
2588 for (var k = 0; k < frames[i].mytweetsID.length; k++) { |
|
2589 |
|
2590 if (frames[i].mytweetsID[k].qualification == j) { |
|
2591 var x = i * frameSize; |
|
2592 var y = this.heightmax - addEheight; |
|
2593 |
|
2594 if (this.yMax > y) { |
|
2595 this.yMax = y; |
|
2596 } |
|
2597 |
|
2598 var e = this.paper.rect(x, y, frameSize - margin, TweetHeight /* height */) |
|
2599 .attr({stroke:"#00","stroke-width":0.1, fill: colors[j]}); |
|
2600 |
|
2601 addEheight += TweetHeight; |
|
2602 |
|
2603 e.color = colors[j]; |
|
2604 e.time = frames[i].mytweetsID[k].timeframe; |
|
2605 e.title = frames[i].mytweetsID[k].title; |
|
2606 e.id = frames[i].mytweetsID[k].cinecast_id; |
|
2607 |
|
2608 this.svgElements[e.id] = e; |
|
2609 |
|
2610 /* |
|
2611 e.mouseover(function(element) { return function (event) { |
|
2612 // event.clientX and event.clientY are to raphael what event.pageX and pageY are to jquery. |
|
2613 self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), event.clientX - 106, event.clientY - 160); |
|
2614 element.displayed = true; |
|
2615 }}(e)).mouseout(function(element) { return function () { |
|
2616 self.TooltipWidget.hide.call(self.TooltipWidget); |
|
2617 }}(e)).mousedown(function () { |
|
2618 self._Popcorn.currentTime(this.time/1000); |
|
2619 self._Popcorn.trigger("IriSP.PolemicTweet.click", this.id); |
|
2620 }); |
|
2621 */ |
|
2622 |
|
2623 IriSP.jQuery(e.node).mouseenter(function(element) { return function (event) { |
|
2624 // event.clientX and event.clientY are to raphael what event.pageX and pageY are to jquery. |
|
2625 self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), event.pageX - 106, event.pageY - 160); |
|
2626 element.displayed = true; |
|
2627 }}(e)).mousedown(function(element) { return function () { |
|
2628 self._Popcorn.currentTime(element.time/1000); |
|
2629 self._Popcorn.trigger("IriSP.PolemicTweet.click", element.id); |
|
2630 } |
|
2631 }(e)); |
|
2632 |
|
2633 IriSP.jQuery(e.node).attr('id', 't' + k + ''); |
|
2634 IriSP.jQuery(e.node).attr('title', frames[i].mytweetsID[k].title); |
|
2635 IriSP.jQuery(e.node).attr('begin', frames[i].mytweetsID[k].timeframe); |
|
2636 } |
|
2637 } |
|
2638 } |
|
2639 } |
|
2640 } |
|
2641 |
|
2642 } |
|
2643 // DRAW UI :: resize border and bgd |
|
2644 this.paperBackground = this.paper.rect(0, 0, this.width, this.heightmax).attr({fill:"#F8F8F8","stroke-width":0.1,opacity: 1}); |
|
2645 |
|
2646 // outer borders |
|
2647 this.outerBorders = []; |
|
2648 this.outerBorders.push(this.paper.rect(0, this.height - 1, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1})); |
|
2649 this.outerBorders.push(this.paper.rect(0, 0, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1})); |
|
2650 |
|
2651 // inner borders |
|
2652 this.innerBorders = []; |
|
2653 this.innerBorders.push(this.paper.rect(1, this.height - 2, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
2654 this.innerBorders.push(this.paper.rect(1, 1, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
2655 this.innerBorders.push(this.paper.rect(1, 1, 1, this.height - 2).attr({fill:"#d0d1d1",stroke: "none",opacity: 0.8})); |
|
2656 this.innerBorders.push(this.paper.rect(this.width - 2, 1, 1, this.height - 2).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
2657 |
|
2658 |
|
2659 |
|
2660 this.paperSlider = this.paper.rect(0, 0, 0, this.heightmax).attr({fill:"#D4D5D5", stroke: "none", opacity: 1}); |
|
2661 |
|
2662 // the small white line displayed over the slider. |
|
2663 this.sliderTip = this.paper.rect(0, 0, 1, this.heightmax).attr({fill:"#fc00ff", stroke: "none", opacity: 1}); |
|
2664 // decalage |
|
2665 // tweetSelection = this.paper.rect(-100,-100,5,5).attr({fill:"#fff",stroke: "none",opacity: 1}); |
|
2666 |
|
2667 |
|
2668 this.paperSlider.toBack(); |
|
2669 this.paperBackground.toBack(); |
|
2670 this.sliderTip.toFront(); |
|
2671 } |
|
2672 |
|
2673 this.selector.mouseleave(IriSP.wrap(this, function() { self.TooltipWidget.hide.call(self.TooltipWidget); })); |
|
2674 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater)); |
|
2675 } |
|
2676 |
|
2677 IriSP.PolemicWidget.prototype.sliderUpdater = function() { |
|
2678 |
|
2679 var time = +this._Popcorn.currentTime(); |
|
2680 var duration = +this._serializer.currentMedia().meta["dc:duration"]; |
|
2681 |
|
2682 this.paperSlider.attr("width", time * (this.width / (duration / 1000))); |
|
2683 |
|
2684 this.sliderTip.attr("x", time * (this.width / (duration / 1000))); |
|
2685 }; |
|
2686 |
|
2687 IriSP.PolemicWidget.prototype.searchHandler = function(searchString) { |
|
2688 if (searchString == "") |
|
2689 return; |
|
2690 |
|
2691 var matches = this._serializer.searchTweetsOccurences(searchString); |
|
2692 |
|
2693 if (IriSP.countProperties(matches) > 0) { |
|
2694 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
2695 } else { |
|
2696 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
2697 } |
|
2698 |
|
2699 for (var id in matches) { |
|
2700 if (this.svgElements.hasOwnProperty(id)) { |
|
2701 var e = this.svgElements[id]; |
|
2702 this.svgElements[id].attr({fill: "#fc00ff"}); |
|
2703 } |
|
2704 } |
|
2705 |
|
2706 // clean up the blocks that were in the previous search |
|
2707 // but who aren't in the current one. |
|
2708 for (var id in this.oldSearchMatches) { |
|
2709 if (!matches.hasOwnProperty(id)) { |
|
2710 var e = this.svgElements[id]; |
|
2711 e.attr({fill: e.color}); |
|
2712 } |
|
2713 } |
|
2714 |
|
2715 this.oldSearchMatches = matches; |
|
2716 }; |
|
2717 |
|
2718 IriSP.PolemicWidget.prototype.searchFieldClearedHandler = function() { |
|
2719 // clean up the blocks that were in the previous search |
|
2720 // but who aren't in the current one. |
|
2721 for (var id in this.oldSearchMatches) { |
|
2722 var e = this.svgElements[id]; |
|
2723 e.attr({fill: e.color}); |
|
2724 } |
|
2725 |
|
2726 }; |
|
2727 |
|
2728 IriSP.PolemicWidget.prototype.searchFieldClosedHandler = function() { |
|
2729 // clean up the blocks that were in the previous search |
|
2730 // but who aren't in the current one. |
|
2731 for (var id in this.oldSearchMatches) { |
|
2732 var e = this.svgElements[id]; |
|
2733 e.attr({fill: e.color}); |
|
2734 } |
|
2735 |
|
2736 }; |
|
2737 |
|
2738 IriSP.SegmentsWidget = function(Popcorn, config, Serializer) { |
|
2739 |
|
2740 var self = this; |
|
2741 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2742 this.oldSearchMatches = []; |
|
2743 |
|
2744 // event handlers |
|
2745 this._Popcorn.listen("IriSP.search", function(searchString) { self.searchHandler.call(self, searchString); }); |
|
2746 this._Popcorn.listen("IriSP.search.closed", function() { self.searchFieldClosedHandler.call(self); }); |
|
2747 this._Popcorn.listen("IriSP.search.cleared", function() { self.searchFieldClearedHandler.call(self); }); |
|
2748 }; |
|
2749 |
|
2750 IriSP.SegmentsWidget.prototype = new IriSP.Widget(); |
|
2751 |
|
2752 /* Get the width of a segment, in pixels. */ |
|
2753 IriSP.SegmentsWidget.prototype.segmentToPixel = function(annotation) { |
|
2754 var begin = Math.round((+ annotation.begin) / 1000); |
|
2755 var end = Math.round((+ annotation.end) / 1000); |
|
2756 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
2757 |
|
2758 var startPourcent = IriSP.timeToPourcent(begin, duration); |
|
2759 var startPixel = Math.floor(this.selector.parent().width() * (startPourcent / 100)); |
|
2760 |
|
2761 var endPourcent = Math.floor(IriSP.timeToPourcent(end, duration) - startPourcent); |
|
2762 var endPixel = Math.floor(this.selector.parent().width() * (endPourcent / 100)); |
|
2763 |
|
2764 return endPixel; |
|
2765 }; |
|
2766 |
|
2767 /* compute the total length of a group of segments */ |
|
2768 IriSP.SegmentsWidget.prototype.segmentsLength = function(segmentsList) { |
|
2769 var self = this; |
|
2770 var total = 0; |
|
2771 |
|
2772 for (var i = 0; i < segmentsList.length; i++) |
|
2773 total += self.segmentToPixel(segmentsList[i].annotation); |
|
2774 |
|
2775 return total; |
|
2776 }; |
|
2777 |
|
2778 IriSP.SegmentsWidget.prototype.draw = function() { |
|
2779 |
|
2780 var self = this; |
|
2781 var annotations = this._serializer._data.annotations; |
|
2782 |
|
2783 this.selector.addClass("Ldt-SegmentsWidget"); |
|
2784 this.selector.append(Mustache.to_html(IriSP.overlay_marker_template)); |
|
2785 |
|
2786 var view_type = this._serializer.getNonTweetIds()[0]; |
|
2787 |
|
2788 this.positionMarker = this.selector.children(":first"); |
|
2789 |
|
2790 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.positionUpdater)); |
|
2791 |
|
2792 |
|
2793 var i = 0; |
|
2794 |
|
2795 var segments_annotations = []; |
|
2796 |
|
2797 for (i = 0; i < annotations.length; i++) { |
|
2798 var annotation = annotations[i]; |
|
2799 |
|
2800 /* filter the annotations whose type is not the one we want */ |
|
2801 if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" |
|
2802 && annotation.meta["id-ref"] != view_type) { |
|
2803 continue; |
|
2804 } |
|
2805 |
|
2806 segments_annotations.push(annotation); |
|
2807 } |
|
2808 |
|
2809 var totalWidth = this.selector.width() - segments_annotations.length; |
|
2810 var lastSegment = IriSP.underscore.max(segments_annotations, function(annotation) { return annotation.end; }); |
|
2811 |
|
2812 for (i = 0; i < segments_annotations.length; i++) { |
|
2813 |
|
2814 var annotation = segments_annotations[i]; |
|
2815 var begin = (+ annotation.begin); |
|
2816 var end = (+ annotation.end); |
|
2817 var duration = this._serializer.currentMedia().meta["dc:duration"]; |
|
2818 var id = annotation.id; |
|
2819 |
|
2820 var startPixel = Math.floor(this.selector.parent().width() * (begin / duration)); |
|
2821 |
|
2822 var endPixel = Math.floor(this.selector.parent().width() * (end / duration)); |
|
2823 |
|
2824 if (annotation.id !== lastSegment.id) |
|
2825 var pxWidth = endPixel - startPixel -1; |
|
2826 else |
|
2827 /* the last segment has no segment following it */ |
|
2828 var pxWidth = endPixel - startPixel; |
|
2829 |
|
2830 var divTitle = (annotation.content.title + " - " + annotation.content.description).substr(0,55); |
|
2831 |
|
2832 if (typeof(annotation.content.color) !== "undefined") |
|
2833 var color = annotation.content.color; |
|
2834 else |
|
2835 var color = annotation.color; |
|
2836 |
|
2837 var hexa_color = IriSP.DEC_HEXA_COLOR(color); |
|
2838 |
|
2839 if (hexa_color === "FFCC00") |
|
2840 hexa_color = "333"; |
|
2841 if (hexa_color.length == 4) |
|
2842 hexa_color = hexa_color + '00'; |
|
2843 |
|
2844 var annotationTemplate = Mustache.to_html(IriSP.annotation_template, |
|
2845 {"divTitle" : divTitle, "id" : id, "startPixel" : startPixel, |
|
2846 "pxWidth" : pxWidth, "hexa_color" : hexa_color, |
|
2847 "seekPlace" : Math.round(begin/1000)}); |
|
2848 |
|
2849 |
|
2850 this.selector.append(annotationTemplate); |
|
2851 |
|
2852 /* add a special class to the last segment and change its border */ |
|
2853 if (annotation.id === lastSegment.id) { |
|
2854 this.selector.find("#" + id).addClass("Ldt-lastSegment"); |
|
2855 this.selector.find(".Ldt-lastSegment").css("border-color", "#" + hexa_color); |
|
2856 } |
|
2857 |
|
2858 IriSP.jQuery("#" + id).fadeTo(0, 0.3); |
|
2859 |
|
2860 IriSP.jQuery("#" + id).mouseover( |
|
2861 /* we wrap the handler in another function because js's scoping |
|
2862 rules are function-based - otherwise, the internal vars like |
|
2863 divTitle are preserved but they are looked-up from the draw |
|
2864 method scope, so after that the loop is run, so they're not |
|
2865 preserved */ |
|
2866 (function(divTitle) { |
|
2867 return function(event) { |
|
2868 IriSP.jQuery(this).animate({opacity: 0.6}, 5); |
|
2869 var offset = IriSP.jQuery(this).offset(); |
|
2870 var correction = IriSP.jQuery(this).outerWidth() / 2; |
|
2871 |
|
2872 var offset_x = offset.left + correction - 106; |
|
2873 if (offset_x < 0) |
|
2874 offset_x = 0; |
|
2875 |
|
2876 self.TooltipWidget.show(divTitle, color, offset_x, event.pageY - 160); |
|
2877 } })(divTitle)).mouseout(function(){ |
|
2878 IriSP.jQuery(this).animate({opacity: 0.3}, 5); |
|
2879 self.TooltipWidget.hide(); |
|
2880 }); |
|
2881 |
|
2882 IriSP.jQuery("#" + id).click(function(_this, annotation) { |
|
2883 return function() { _this.clickHandler(annotation)}; |
|
2884 }(this, annotation)); |
|
2885 } |
|
2886 }; |
|
2887 |
|
2888 /* restores the view after a search */ |
|
2889 IriSP.SegmentsWidget.prototype.clear = function() { |
|
2890 this.selector.children(".Ldt-iri-chapter").animate({opacity:0.3}, 100); |
|
2891 }; |
|
2892 |
|
2893 IriSP.SegmentsWidget.prototype.clickHandler = function(annotation) { |
|
2894 this._Popcorn.trigger("IriSP.SegmentsWidget.click", annotation.id); |
|
2895 var begin = (+ annotation.begin) / 1000; |
|
2896 this._Popcorn.currentTime(Math.round(begin)); |
|
2897 }; |
|
2898 |
|
2899 IriSP.SegmentsWidget.prototype.searchHandler = function(searchString) { |
|
2900 |
|
2901 if (searchString == "") |
|
2902 return; |
|
2903 |
|
2904 var matches = this._serializer.searchOccurences(searchString); |
|
2905 |
|
2906 if (IriSP.countProperties(matches) > 0) { |
|
2907 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
2908 } else { |
|
2909 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
2910 } |
|
2911 |
|
2912 // un-highlight all the blocks |
|
2913 this.selector.children(".Ldt-iri-chapter").css("opacity", 0.1); |
|
2914 |
|
2915 // then highlight the ones with matches. |
|
2916 for (var id in matches) { |
|
2917 var factor = 0.5 + matches[id] * 0.2; |
|
2918 this.selector.find("#"+id).dequeue(); |
|
2919 this.selector.find("#"+id).animate({opacity:factor}, 200); |
|
2920 } |
|
2921 |
|
2922 |
|
2923 this.oldSearchMatches = matches; |
|
2924 }; |
|
2925 |
|
2926 IriSP.SegmentsWidget.prototype.searchFieldClearedHandler = function() { |
|
2927 this.clear(); |
|
2928 }; |
|
2929 |
|
2930 IriSP.SegmentsWidget.prototype.searchFieldClosedHandler = function() { |
|
2931 this.clear(); |
|
2932 }; |
|
2933 |
|
2934 IriSP.SegmentsWidget.prototype.positionUpdater = function() { |
|
2935 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
2936 var time = this._Popcorn.currentTime(); |
|
2937 //var position = ((time / duration) * 100).toFixed(2); |
|
2938 var position = ((time / duration) * 100).toFixed(2); |
|
2939 |
|
2940 this.positionMarker.css("left", position + "%"); |
|
2941 }; |
|
2942 IriSP.SliderWidget = function(Popcorn, config, Serializer) { |
|
2943 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2944 }; |
|
2945 |
|
2946 IriSP.SliderWidget.prototype = new IriSP.Widget(); |
|
2947 |
|
2948 IriSP.SliderWidget.prototype.draw = function() { |
|
2949 var self = this; |
|
2950 |
|
2951 this.selector.append(Mustache.to_html(IriSP.sliderWidget_template, {})); |
|
2952 this.selector.addClass("Ldt-SliderMinimized"); |
|
2953 |
|
2954 this.sliderBackground = this.selector.find(".Ldt-sliderBackground"); |
|
2955 this.sliderForeground = this.selector.find(".Ldt-sliderForeground"); |
|
2956 this.positionMarker = this.selector.find(".Ldt-sliderPositionMarker"); |
|
2957 |
|
2958 |
|
2959 // a special variable to stop methods from tinkering |
|
2960 // with the positionMarker when the user is dragging it |
|
2961 this.draggingOngoing = false; |
|
2962 |
|
2963 // another special variable used by the timeout handler to |
|
2964 // open or close the slider. |
|
2965 this.sliderMaximized = false; |
|
2966 this.timeOutId = null; |
|
2967 |
|
2968 |
|
2969 this.positionMarker.draggable({axis: "x", |
|
2970 start: IriSP.wrap(this, this.positionMarkerDraggingStartedHandler), |
|
2971 stop: IriSP.wrap(this, this.positionMarkerDraggedHandler), |
|
2972 containment: "parent" |
|
2973 }); |
|
2974 this.positionMarker.css("position", "absolute"); |
|
2975 |
|
2976 this.sliderBackground.click(function(event) { self.backgroundClickHandler.call(self, event); }); |
|
2977 this.sliderForeground.click(function(event) { self.foregroundClickHandler.call(self, event); }); |
|
2978 |
|
2979 this.selector.hover(IriSP.wrap(this, this.mouseOverHandler), IriSP.wrap(this, this.mouseOutHandler)); |
|
2980 |
|
2981 // update the positions |
|
2982 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater)); |
|
2983 |
|
2984 // special messages : |
|
2985 this._Popcorn.listen("IriSP.PlayerWidget.MouseOver", IriSP.wrap(this, this.mouseOverHandler)); |
|
2986 this._Popcorn.listen("IriSP.PlayerWidget.MouseOut", IriSP.wrap(this, this.mouseOutHandler)); |
|
2987 }; |
|
2988 |
|
2989 /* update the slider and the position marker as time passes */ |
|
2990 IriSP.SliderWidget.prototype.sliderUpdater = function() { |
|
2991 if(this.draggingOngoing || this._disableUpdate) |
|
2992 return; |
|
2993 |
|
2994 var time = this._Popcorn.currentTime(); |
|
2995 |
|
2996 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
2997 var percent = ((time / duration) * 100).toFixed(2); |
|
2998 |
|
2999 /* we do these complicated calculations to center exactly |
|
3000 the position Marker */ |
|
3001 var pixels_to_percents = 100 / this.selector.width(); /* how much is a pixel in percents */ |
|
3002 var positionMarker_width = this.positionMarker.width(); |
|
3003 var correction = (pixels_to_percents * positionMarker_width) / 2; |
|
3004 |
|
3005 var newPos = percent - correction; |
|
3006 if (newPos <= 0) |
|
3007 newPos = 0; |
|
3008 |
|
3009 this.sliderForeground.css("width", percent + "%"); |
|
3010 this.positionMarker.css("left", newPos + "%"); |
|
3011 |
|
3012 }; |
|
3013 |
|
3014 IriSP.SliderWidget.prototype.backgroundClickHandler = function(event) { |
|
3015 /* this piece of code is a little bit convoluted - here's how it works : |
|
3016 we want to handle clicks on the progress bar and convert those to seeks in the media. |
|
3017 However, jquery only gives us a global position, and we want a number of pixels relative |
|
3018 to our container div, so we get the parent position, and compute an offset to this position, |
|
3019 and finally compute the progress ratio in the media. |
|
3020 Finally we multiply this ratio with the duration to get the correct time |
|
3021 */ |
|
3022 |
|
3023 var parentOffset = this.sliderBackground.parent().offset(); |
|
3024 var width = this.sliderBackground.width(); |
|
3025 var relX = event.pageX - parentOffset.left; |
|
3026 |
|
3027 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
3028 var newTime = ((relX / width) * duration).toFixed(2); |
|
3029 |
|
3030 this._Popcorn.currentTime(newTime); |
|
3031 }; |
|
3032 |
|
3033 /* same function as the previous one, except that it handles clicks |
|
3034 on the foreground element */ |
|
3035 IriSP.SliderWidget.prototype.foregroundClickHandler = function(event) { |
|
3036 var parentOffset = this.sliderForeground.parent().offset(); |
|
3037 var width = this.sliderBackground.width(); |
|
3038 var relX = event.pageX - parentOffset.left; |
|
3039 |
|
3040 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
3041 var newTime = ((relX / width) * duration).toFixed(2); |
|
3042 |
|
3043 this._Popcorn.currentTime(newTime); |
|
3044 }; |
|
3045 |
|
3046 /* handles mouse over the slider */ |
|
3047 IriSP.SliderWidget.prototype.mouseOverHandler = function(event) { |
|
3048 |
|
3049 if (this.timeOutId !== null) { |
|
3050 window.clearTimeout(this.timeOutId); |
|
3051 } |
|
3052 |
|
3053 this.sliderMaximized = true; |
|
3054 |
|
3055 this.sliderBackground.animate({"height": "9px"}, 100); |
|
3056 this.sliderForeground.animate({"height": "9px"}, 100); |
|
3057 this.positionMarker.animate({"height": "9px", "width": "9px"}, 100); |
|
3058 //this.positionMarker.css("margin-top", "-4px"); |
|
3059 |
|
3060 // this.selector.removeClass("Ldt-SliderMinimized"); |
|
3061 // this.selector.addClass("Ldt-SliderMaximized"); |
|
3062 }; |
|
3063 |
|
3064 /* handles when the mouse leaves the slider */ |
|
3065 IriSP.SliderWidget.prototype.mouseOutHandler = function(event) { |
|
3066 |
|
3067 this.timeOutId = window.setTimeout(IriSP.wrap(this, this.minimizeOnTimeout), |
|
3068 IriSP.widgetsDefaults.SliderWidget.minimize_period); |
|
3069 }; |
|
3070 |
|
3071 IriSP.SliderWidget.prototype.minimizeOnTimeout = function(event) { |
|
3072 this.sliderBackground.animate({"height": "5px"}, 100); |
|
3073 this.sliderForeground.animate({"height": "5px"}, 100); |
|
3074 this.positionMarker.animate({"height": "5px", "width": "5px"}, 100); |
|
3075 this.positionMarker.css("margin-top", "0px"); |
|
3076 this.sliderMinimized = true; |
|
3077 |
|
3078 // this.selector.removeClass("Ldt-SliderMaximized"); |
|
3079 // this.selector.addClass("Ldt-SliderMinimized"); |
|
3080 |
|
3081 }; |
|
3082 |
|
3083 // called when the user starts dragging the position indicator |
|
3084 IriSP.SliderWidget.prototype.positionMarkerDraggingStartedHandler = function(event, ui) { |
|
3085 this.draggingOngoing = true; |
|
3086 }; |
|
3087 |
|
3088 IriSP.SliderWidget.prototype.positionMarkerDraggedHandler = function(event, ui) { |
|
3089 this._disableUpdate = true; // disable slider position updates while dragging is ongoing. |
|
3090 window.setTimeout(IriSP.wrap(this, function() { this._disableUpdate = false; }), 500); |
|
3091 |
|
3092 var parentOffset = this.sliderForeground.parent().offset(); |
|
3093 var width = this.sliderBackground.width(); |
|
3094 var relX = event.pageX - parentOffset.left; |
|
3095 |
|
3096 var duration = this._serializer.currentMedia().meta["dc:duration"] / 1000; |
|
3097 var newTime = ((relX / width) * duration).toFixed(2); |
|
3098 |
|
3099 this._Popcorn.currentTime(newTime); |
|
3100 |
|
3101 this.draggingOngoing = false; |
|
3102 }; |
|
3103 |
|
3104 /* this widget displays a small tooltip */ |
|
3105 IriSP.TooltipWidget = function(Popcorn, config, Serializer) { |
|
3106 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3107 this._shown = false; |
|
3108 this._displayedText = ""; |
|
3109 this._hideTimeout = -1; |
|
3110 }; |
|
3111 |
|
3112 |
|
3113 IriSP.TooltipWidget.prototype = new IriSP.Widget(); |
|
3114 |
|
3115 IriSP.TooltipWidget.prototype.draw = function() { |
|
3116 var templ = Mustache.to_html(IriSP.tooltipWidget_template); |
|
3117 |
|
3118 this.selector.append(templ); |
|
3119 this.hide(); |
|
3120 |
|
3121 }; |
|
3122 |
|
3123 IriSP.TooltipWidget.prototype.clear = function() { |
|
3124 this.selector.find(".tiptext").text(""); |
|
3125 }; |
|
3126 |
|
3127 IriSP.TooltipWidget.prototype.show = function(text, color, x, y) { |
|
3128 |
|
3129 if (this._displayedText == text) |
|
3130 return; |
|
3131 |
|
3132 this.selector.find(".tipcolor").css("background-color", color); |
|
3133 this._displayedText = text; |
|
3134 this.selector.find(".tiptext").text(text); |
|
3135 //this.selector.find(".tip").css("left", x).css("top", y); |
|
3136 this.selector.find(".tip").css("left", x).css("top", y); |
|
3137 this.selector.find(".tip").show(); |
|
3138 this._shown = true; |
|
3139 }; |
|
3140 |
|
3141 IriSP.TooltipWidget.prototype.hide = function() { |
|
3142 this.selector.find(".tip").hide(); |
|
3143 this._shown = false; |
|
3144 };/* a widget that displays tweet - used in conjunction with the polemicWidget */ |
|
3145 |
|
3146 IriSP.TweetsWidget = function(Popcorn, config, Serializer) { |
|
3147 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3148 |
|
3149 this._displayingTweet = false; |
|
3150 this._timeoutId = undefined; |
|
3151 }; |
|
3152 |
|
3153 |
|
3154 IriSP.TweetsWidget.prototype = new IriSP.Widget(); |
|
3155 |
|
3156 |
|
3157 IriSP.TweetsWidget.prototype.drawTweet = function(annotation) { |
|
3158 |
|
3159 var title = IriSP.formatTweet(annotation.content.title); |
|
3160 var img = annotation.content.img.src; |
|
3161 if (typeof(img) === "undefined" || img === "" || img === "None") { |
|
3162 img = IriSP.widgetsDefaults.TweetsWidget.default_profile_picture; |
|
3163 } |
|
3164 |
|
3165 var imageMarkup = IriSP.templToHTML("<img src='{{src}}' alt='user image'></img>", |
|
3166 {src : img}); |
|
3167 |
|
3168 if (typeof(annotation.meta["dc:source"].content) !== "undefined") { |
|
3169 var tweetContents = JSON.parse(annotation.meta["dc:source"].content); |
|
3170 var creator = tweetContents.user.screen_name; |
|
3171 var real_name = tweetContents.user.name; |
|
3172 |
|
3173 imageMarkup = IriSP.templToHTML("<a href='http://twitter.com/{{creator}}'><img src='{{src}}' alt='user image'></img></a>", |
|
3174 {src : img, creator: creator}); |
|
3175 |
|
3176 var formatted_date = new Date(tweetContents.created_at).toLocaleDateString(); |
|
3177 title = IriSP.templToHTML("<a class='Ldt-tweet_userHandle' href='http://twitter.com/{{creator}}'>@{{creator}}</a> - " + |
|
3178 "<div class='Ldt-tweet_realName'>{{real_name}}</div>" + |
|
3179 "<div class='Ldt-tweet_tweetContents'>{{{ contents }}}</div>" + |
|
3180 "<div class='Ldt-tweet_date'>{{ date }}</div>", |
|
3181 {creator: creator, real_name: real_name, contents : title, date : formatted_date}); |
|
3182 |
|
3183 this.selector.find(".Ldt-TweetReply").attr("href", "http://twitter.com/home?status=@" + creator + ":%20"); |
|
3184 |
|
3185 |
|
3186 var rtText = Mustache.to_html("http://twitter.com/home?status=RT @{{creator}}: {{text}}", |
|
3187 {creator: creator, text: IriSP.encodeURI(annotation.content.title)}); |
|
3188 this.selector.find(".Ldt-Retweet").attr("href", rtText); |
|
3189 } |
|
3190 |
|
3191 this.selector.find(".Ldt-tweetContents").html(title); |
|
3192 this.selector.find(".Ldt-tweetAvatar").html(imageMarkup); |
|
3193 this.selector.show("blind", 250); |
|
3194 }; |
|
3195 |
|
3196 IriSP.TweetsWidget.prototype.displayTweet = function(annotation) { |
|
3197 if (this._displayingTweet === false) { |
|
3198 this._displayingTweet = true; |
|
3199 } else { |
|
3200 window.clearTimeout(this._timeoutId); |
|
3201 } |
|
3202 |
|
3203 this.drawTweet(annotation); |
|
3204 |
|
3205 var time = this._Popcorn.currentTime(); |
|
3206 this._timeoutId = window.setTimeout(IriSP.wrap(this, this.clearPanel), IriSP.widgetsDefaults.TweetsWidget.tweet_display_period); |
|
3207 }; |
|
3208 |
|
3209 |
|
3210 IriSP.TweetsWidget.prototype.clearPanel = function() { |
|
3211 this._displayingTweet = false; |
|
3212 this._timeoutId = undefined; |
|
3213 this.closePanel(); |
|
3214 |
|
3215 }; |
|
3216 |
|
3217 IriSP.TweetsWidget.prototype.closePanel = function() { |
|
3218 if (this._timeoutId != undefined) { |
|
3219 /* we're called from the "close window" link */ |
|
3220 /* cancel the timeout */ |
|
3221 window.clearTimeout(this._timeoutId); |
|
3222 this._timeoutId = null; |
|
3223 } |
|
3224 |
|
3225 this.selector.hide("blind", 400); |
|
3226 |
|
3227 }; |
|
3228 |
|
3229 /* cancel the timeout if the user clicks on the keep panel open button */ |
|
3230 IriSP.TweetsWidget.prototype.keepPanel = function() { |
|
3231 if (this._timeoutId != undefined) { |
|
3232 /* we're called from the "close window" link */ |
|
3233 /* cancel the timeout */ |
|
3234 window.clearTimeout(this._timeoutId); |
|
3235 this._timeoutId = null; |
|
3236 } |
|
3237 }; |
|
3238 |
|
3239 IriSP.TweetsWidget.prototype.draw = function() { |
|
3240 var _this = this; |
|
3241 |
|
3242 var tweetMarkup = IriSP.templToHTML(IriSP.tweetWidget_template, {"share_template" : IriSP.share_template}); |
|
3243 this.selector.append(tweetMarkup); |
|
3244 this.selector.hide(); |
|
3245 this.selector.find(".Ldt-tweetWidgetMinimize").click(IriSP.wrap(this, this.closePanel)); |
|
3246 this.selector.find(".Ldt-tweetWidgetKeepOpen").click(IriSP.wrap(this, this.keepPanel)); |
|
3247 |
|
3248 this._Popcorn.listen("IriSP.PolemicTweet.click", IriSP.wrap(this, this.PolemicTweetClickHandler)); |
|
3249 }; |
|
3250 |
|
3251 IriSP.TweetsWidget.prototype.PolemicTweetClickHandler = function(tweet_id) { |
|
3252 var index, annotation; |
|
3253 for (index in this._serializer._data.annotations) { |
|
3254 annotation = this._serializer._data.annotations[index]; |
|
3255 |
|
3256 if (annotation.id === tweet_id) |
|
3257 break; |
|
3258 } |
|
3259 |
|
3260 if (annotation.id !== tweet_id) |
|
3261 /* we haven't found it */ |
|
3262 return; |
|
3263 |
|
3264 this.displayTweet(annotation); |
|
3265 return; |
|
3266 }; |
|
3267 |
|
3268 IriSP.JSONSerializer = function(DataLoader, url) { |
|
3269 IriSP.Serializer.call(this, DataLoader, url); |
|
3270 }; |
|
3271 |
|
3272 IriSP.JSONSerializer.prototype = new IriSP.Serializer(); |
|
3273 |
|
3274 IriSP.JSONSerializer.prototype.serialize = function(data) { |
|
3275 return JSON.stringify(data); |
|
3276 }; |
|
3277 |
|
3278 IriSP.JSONSerializer.prototype.deserialize = function(data) { |
|
3279 return JSON.parse(data); |
|
3280 }; |
|
3281 |
|
3282 IriSP.JSONSerializer.prototype.sync = function(callback) { |
|
3283 /* we don't have to do much because jQuery handles json for us */ |
|
3284 |
|
3285 var self = this; |
|
3286 |
|
3287 var fn = function(data) { |
|
3288 self._data = data; |
|
3289 // sort the data too |
|
3290 self._data["annotations"].sort(function(a, b) |
|
3291 { var a_begin = +a.begin; |
|
3292 var b_begin = +b.begin; |
|
3293 return a_begin - b_begin; |
|
3294 }); |
|
3295 |
|
3296 callback(data); |
|
3297 }; |
|
3298 |
|
3299 this._DataLoader.get(this._url, fn); |
|
3300 }; |
|
3301 |
|
3302 IriSP.JSONSerializer.prototype.currentMedia = function() { |
|
3303 return this._data.medias[0]; /* FIXME: don't hardcode it */ |
|
3304 }; |
|
3305 |
|
3306 /* this function searches for an annotation which matches title, description and keyword |
|
3307 "" matches any field. |
|
3308 Note: it ignores tweets. |
|
3309 */ |
|
3310 IriSP.JSONSerializer.prototype.searchAnnotations = function(title, description, keyword) { |
|
3311 /* we can have many types of annotations. We want search to only look for regular segments */ |
|
3312 /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either |
|
3313 null or undefined. |
|
3314 */ |
|
3315 var view; |
|
3316 |
|
3317 if (typeof(this._data.views) !== "undefined" && this._data.views !== null) |
|
3318 view = this._data.views[0]; |
|
3319 |
|
3320 var searchViewType = ""; |
|
3321 |
|
3322 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
3323 searchViewType = view.annotation_types[0]; |
|
3324 } |
|
3325 |
|
3326 var filterfn = function(annotation) { |
|
3327 if( searchViewType != "" && |
|
3328 typeof(annotation.meta) !== "undefined" && |
|
3329 typeof(annotation.meta["id-ref"]) !== "undefined" && |
|
3330 annotation.meta["id-ref"] !== searchViewType) { |
|
3331 return true; // don't pass |
|
3332 } else { |
|
3333 return false; |
|
3334 } |
|
3335 }; |
|
3336 |
|
3337 return this.searchAnnotationsFilter(title, description, keyword, filterfn); |
|
3338 |
|
3339 }; |
|
3340 |
|
3341 /* only look for tweets */ |
|
3342 IriSP.JSONSerializer.prototype.searchTweets = function(title, description, keyword) { |
|
3343 /* we can have many types of annotations. We want search to only look for regular segments */ |
|
3344 /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either |
|
3345 null or undefined. |
|
3346 */ |
|
3347 var view; |
|
3348 |
|
3349 if (typeof(this._data.views) !== "undefined" && this._data.views !== null) |
|
3350 view = this._data.views[0]; |
|
3351 |
|
3352 var searchViewType = ""; |
|
3353 |
|
3354 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
3355 searchViewType = view.annotation_types[0]; |
|
3356 } |
|
3357 |
|
3358 var filterfn = function(annotation) { |
|
3359 if( searchViewType != "" && |
|
3360 typeof(annotation.meta) !== "undefined" && |
|
3361 typeof(annotation.meta["id-ref"]) !== "undefined" && |
|
3362 annotation.meta["id-ref"] !== searchViewType) { |
|
3363 return false; // pass |
|
3364 } else { |
|
3365 return true; |
|
3366 } |
|
3367 }; |
|
3368 |
|
3369 return this.searchAnnotationsFilter(title, description, keyword, filterfn); |
|
3370 |
|
3371 }; |
|
3372 |
|
3373 /* |
|
3374 the previous function call this one, which is more general: |
|
3375 */ |
|
3376 IriSP.JSONSerializer.prototype.searchAnnotationsFilter = function(title, description, keyword, filter) { |
|
3377 |
|
3378 var rTitle; |
|
3379 var rDescription; |
|
3380 var rKeyword; |
|
3381 /* match anything if given the empty string */ |
|
3382 if (title == "") |
|
3383 title = ".*"; |
|
3384 if (description == "") |
|
3385 description = ".*"; |
|
3386 if (keyword == "") |
|
3387 keyword = ".*"; |
|
3388 |
|
3389 rTitle = new RegExp(title, "i"); |
|
3390 rDescription = new RegExp(description, "i"); |
|
3391 rKeyword = new RegExp(keyword, "i"); |
|
3392 |
|
3393 var ret_array = []; |
|
3394 |
|
3395 var i; |
|
3396 for (i in this._data.annotations) { |
|
3397 var annotation = this._data.annotations[i]; |
|
3398 |
|
3399 /* filter the annotations whose type is not the one we want */ |
|
3400 if (filter(annotation)) { |
|
3401 continue; |
|
3402 } |
|
3403 |
|
3404 if (rTitle.test(annotation.content.title) && |
|
3405 rDescription.test(annotation.content.description)) { |
|
3406 /* FIXME : implement keyword support */ |
|
3407 ret_array.push(annotation); |
|
3408 } |
|
3409 } |
|
3410 |
|
3411 return ret_array; |
|
3412 }; |
|
3413 |
|
3414 /* breaks a string in words and searches each of these words. Returns an array |
|
3415 of objects with the id of the annotation and its number of occurences. |
|
3416 |
|
3417 FIXME: optimize ? seems to be n^2 in the worst case. |
|
3418 */ |
|
3419 IriSP.JSONSerializer.prototype.searchOccurences = function(searchString) { |
|
3420 var ret = { }; |
|
3421 var keywords = searchString.split(/\s+/); |
|
3422 |
|
3423 for (var i in keywords) { |
|
3424 var keyword = keywords[i]; |
|
3425 |
|
3426 // search this keyword in descriptions and title |
|
3427 var found_annotations = [] |
|
3428 found_annotations = found_annotations.concat(this.searchAnnotations(keyword, "", "")); |
|
3429 found_annotations = found_annotations.concat(this.searchAnnotations("", keyword, "")); |
|
3430 |
|
3431 for (var j in found_annotations) { |
|
3432 var current_annotation = found_annotations[j]; |
|
3433 |
|
3434 if (!ret.hasOwnProperty(current_annotation.id)) { |
|
3435 ret[current_annotation.id] = 1; |
|
3436 } else { |
|
3437 ret[current_annotation.id] += 1; |
|
3438 } |
|
3439 |
|
3440 } |
|
3441 |
|
3442 }; |
|
3443 |
|
3444 return ret; |
|
3445 }; |
|
3446 |
|
3447 /* breaks a string in words and searches each of these words. Returns an array |
|
3448 of objects with the id of the annotation and its number of occurences. |
|
3449 |
|
3450 FIXME: optimize ? seems to be n^2 in the worst case. |
|
3451 */ |
|
3452 IriSP.JSONSerializer.prototype.searchTweetsOccurences = function(searchString) { |
|
3453 var ret = { }; |
|
3454 var keywords = searchString.split(/\s+/); |
|
3455 |
|
3456 for (var i in keywords) { |
|
3457 var keyword = keywords[i]; |
|
3458 |
|
3459 // search this keyword in descriptions and title |
|
3460 var found_annotations = [] |
|
3461 found_annotations = found_annotations.concat(this.searchTweets(keyword, "", "")); |
|
3462 found_annotations = found_annotations.concat(this.searchTweets("", keyword, "")); |
|
3463 |
|
3464 for (var j in found_annotations) { |
|
3465 var current_annotation = found_annotations[j]; |
|
3466 |
|
3467 if (!ret.hasOwnProperty(current_annotation.id)) { |
|
3468 ret[current_annotation.id] = 1; |
|
3469 } else { |
|
3470 ret[current_annotation.id] += 1; |
|
3471 } |
|
3472 |
|
3473 } |
|
3474 |
|
3475 }; |
|
3476 |
|
3477 return ret; |
|
3478 }; |
|
3479 |
|
3480 /* takes the currentTime and returns all the annotations that are displayable at the moment |
|
3481 NB: only takes account the first type of annotations - ignores tweets |
|
3482 currentTime is in seconds. |
|
3483 */ |
|
3484 |
|
3485 IriSP.JSONSerializer.prototype.currentAnnotations = function(currentTime) { |
|
3486 var view; |
|
3487 var currentTimeMs = 1000 * currentTime; |
|
3488 |
|
3489 if (typeof(this._data.views) !== "undefined" && this._data.views !== null) |
|
3490 view = this._data.views[0]; |
|
3491 |
|
3492 var view_type = ""; |
|
3493 |
|
3494 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length >= 1) { |
|
3495 view_type = view.annotation_types[0]; |
|
3496 } |
|
3497 |
|
3498 var ret_array = []; |
|
3499 |
|
3500 var i; |
|
3501 |
|
3502 for (i in this._data.annotations) { |
|
3503 var annotation = this._data.annotations[i]; |
|
3504 |
|
3505 if (annotation.meta["id-ref"] === view_type && annotation.begin <= currentTimeMs && annotation.end >= currentTimeMs) |
|
3506 ret_array.push(annotation); |
|
3507 } |
|
3508 |
|
3509 return ret_array; |
|
3510 }; |
|
3511 |
|
3512 |
|
3513 /* this function returns a list of ids of tweet lines */ |
|
3514 IriSP.JSONSerializer.prototype.getTweetIds = function() { |
|
3515 if (typeof(this._data.lists) === "undefined" || this._data.lists === null) |
|
3516 return []; |
|
3517 |
|
3518 var tweetsId = []; |
|
3519 |
|
3520 /* first get the list containing the tweets */ |
|
3521 var tweets = IriSP.underscore.filter(this._data.lists, function(entry) { return entry.id.indexOf("tweet") !== -1 }); |
|
3522 |
|
3523 // FIXME: collect tweets from multiple sources ? |
|
3524 tweetsId = IriSP.underscore.pluck(tweets[0].items, "id-ref"); |
|
3525 |
|
3526 return tweetsId; |
|
3527 }; |
|
3528 |
|
3529 /* this function returns a list of lines which are not tweet lines */ |
|
3530 IriSP.JSONSerializer.prototype.getNonTweetIds = function() { |
|
3531 if (typeof(this._data.lists) === "undefined" || this._data.lists === null) |
|
3532 return []; |
|
3533 |
|
3534 /* get all the ids */ |
|
3535 var ids = IriSP.underscore.map(this._data.lists, function(entry) { |
|
3536 return IriSP.underscore.pluck(entry.items, "id-ref"); }); |
|
3537 |
|
3538 var illegal_values = this.getTweetIds(); |
|
3539 return IriSP.underscore.difference(ids, illegal_values); |
|
3540 |
|
3541 }; |
|