cleanup.
[Portfolio.git] / skins / photo_film_viewer.js
1 /*
2 * © 2008 Benoît Pin – Centre de recherche en informatique – École des mines de Paris
3 * http://plinn.org
4 * Licence Creative Commons http://creativecommons.org/licenses/by-nc/2.0/
5 *
6 *
7 */
8
9 var FilmSlider;
10
11 (function(){
12
13 var keyLeft = 37, keyRight = 39;
14 var isTextMime = /^text\/.+/i;
15 var isAddToSelection = /.*\/add_to_selection$/;
16 var imgRequestedSize = /size=(\d+)/;
17 var DEFAULT_IMAGE_SIZES = [500, 600, 800];
18
19 FilmSlider = function(filmBar, slider, ctxInfos, image, toolbar, breadcrumbs) {
20 var thisSlider = this;
21 this.filmBar = filmBar;
22 var film = filmBar.firstChild;
23 if (film.nodeType == 3)
24 film = film.nextSibling;
25 this.film = film;
26 this.slider = slider;
27 this.rail = slider.parentNode;
28 this.sliderSpeedRatio = undefined;
29 this.sliderRatio = undefined;
30 this.selectedSlide = undefined;
31 this.selectedSlideInSelection = undefined;
32 this.cartSlide = document.getElementById('cart_slide');
33 this.image = image;
34 this.stretchable = image.parentNode;
35 this.viewMode = 'medium';
36
37 this.buttons = new Array();
38 this.toolbar = toolbar;
39 var bcElements = breadcrumbs.getElementsByTagName('a');
40 this.lastBCElement = bcElements[bcElements.length-1];
41 var imgSrcParts = image.src.split('/');
42 this.lastBCElement.innerHTML = imgSrcParts[imgSrcParts.length-2];
43
44 var buttons = toolbar.getElementsByTagName('img');
45 var b, name;
46 for (var i=0 ; i<buttons.length ; i++) {
47 b = buttons[i];
48 name = b.getAttribute('name');
49 if (name)
50 this.buttons[name] = b;
51 }
52
53 this.pendingImage = new Image();
54 this.pendingImage.onload = function() {
55 thisSlider.refreshImage();
56 };
57 this.initialized = false;
58
59 with(this.slider.style) {
60 left='0';
61 top='0';
62 }
63 with(this.film.style) {
64 left='0';
65 top='0';
66 }
67
68 this.filmLength = ctxInfos.filmLength;
69 this.center = ctxInfos.center;
70 this.slideSize = ctxInfos.slideSize;
71 this.ctxUrlTranslation = ctxInfos.ctxUrlTranslation;
72
73 this.ddHandlers = {'down' : function(evt){thisSlider.mouseDownHandler(evt);},
74 'move' : function(evt){thisSlider.mouseMoveHandler(evt);},
75 'up' : function(evt){thisSlider.mouseUpHandler(evt);},
76 'out' : function(evt){thisSlider.mouseOutHandler(evt);}
77 };
78
79 this.resizeSlider();
80 this.addEventListeners()
81 };
82
83
84 FilmSlider.prototype.resizeSlider = function(evt) {
85 var filmBarWidth = getObjectWidth(this.filmBar);
86 if (!filmBarWidth){
87 var thisSlider = this;
88 addListener(window, 'load', function(evt){thisSlider.resizeSlider(evt);});
89 return;
90 }
91
92 var filmWidth = this.slideSize * this.filmLength;
93 var sliderRatio = this.sliderRatio = filmBarWidth / filmWidth;
94 var sliderWidth = filmBarWidth * sliderRatio;
95 this.rail.style.width = filmBarWidth + 'px';
96 this.rail.style.display = 'block';
97 this.rail.style.visibility = 'visible';
98 if (sliderRatio < 1) {
99 this.slider.style.width = Math.round(sliderWidth) + 'px';
100 this.slider.style.visibility = 'visible';
101 }
102 else {
103 this.slider.style.visibility = 'hidden';
104 }
105
106 this.winSize = {'width' : getWindowWidth(),
107 'height' : getWindowHeight()};
108 this.maxRightPosition = filmBarWidth - sliderWidth
109 this.sliderSpeedRatio = - (filmBarWidth - sliderWidth) / (filmWidth - filmBarWidth);
110 if (!this.initialized) {
111 this.centerSlide(this.center);
112 this.selectedSlide = this.filmBar.getElementsByTagName('img')[this.center].parentNode;
113 this.initialized = true;
114 }
115 };
116
117 FilmSlider.prototype.fitToScreen = function(evt) {
118 this._fitToScreen();
119 var thisSlider = this;
120 addListener(window, 'resize', function(evt){thisSlider._fitToScreen();});
121 };
122
123 FilmSlider.prototype._fitToScreen = function(evt) {
124 var wh = getWindowHeight();
125 var rb = getObjectTop(this.rail) + getObjectHeight(this.rail); // rail bottom
126 var delta = wh - rb
127 var sh = getObjectHeight(this.stretchable);
128 var newSize = sh + delta;
129 this.stretchable.style.height = newSize + 'px';
130
131 var ratio = this.image.height / this.image.width;
132 var bestFitSize = this.getBestFitSize(ratio);
133 var currentSize = parseInt(imgRequestedSize.exec(this.image.src)[1]);
134 if (currentSize != bestFitSize) {
135 var src = this.image.src.replace(imgRequestedSize, 'size=' + bestFitSize);
136 this.pendingImage.src = src;
137 }
138 };
139
140 FilmSlider.prototype.getBestFitSize = function(ratio) {
141 var fw = getObjectWidth(this.stretchable) - 1;
142 var fh = getObjectHeight(this.stretchable) - 1;
143
144 var i, irw, irh;
145 if (ratio < 1) {
146 for (i=DEFAULT_IMAGE_SIZES.length -1 ; i>0 ; i--) {
147 irw = DEFAULT_IMAGE_SIZES[i];
148 irh = irw * ratio;
149 if (irw <= fw && irh <= fh)
150 break;
151 }
152 }
153 else {
154 for (i=DEFAULT_IMAGE_SIZES.length -1 ; i>0 ; i--) {
155 irh = DEFAULT_IMAGE_SIZES[i];
156 irw = irh / ratio;
157 if (irw <= fw && irh <= fh)
158 break;
159 }
160 }
161 return DEFAULT_IMAGE_SIZES[i];
162 };
163
164 FilmSlider.prototype.centerSlide = function(slideIndex) {
165 if (this.sliderRatio > 1)
166 return;
167 var filmBarWidth = getObjectWidth(this.filmBar);
168 var x = slideIndex * this.slideSize
169 x = x - (filmBarWidth - this.slideSize) / 2.0;
170 x = x * this.sliderSpeedRatio;
171 var p = new Point( -x, 0 )
172 this.setSliderPosition(p);
173 };
174
175 FilmSlider.prototype.setSliderPosition = function(point) {
176 if(point.x < 0)
177 point.x = 0;
178 if (point.x > this.maxRightPosition)
179 point.x = this.maxRightPosition;
180 this.slider.style.left = point.x + 'px';
181 this.setFilmPosition(point);
182 };
183
184 FilmSlider.prototype.setFilmPosition = function(point) {
185 this.film.style.left = point.x / this.sliderSpeedRatio + 'px';
186 };
187
188 FilmSlider.prototype.getSliderPosition = function() {
189 var x = parseInt(this.slider.style.left);
190 var y = parseInt(this.slider.style.top);
191 var p = new Point(x, y);
192 return p;
193 };
194
195 FilmSlider.prototype.getFilmPosition = function() {
196 var x = parseInt(this.film.style.left);
197 var y = parseInt(this.film.style.top);
198 var p = new Point(x, y);
199 return p;
200 };
201
202 FilmSlider.prototype.loadSibling = function(previous) {
203 var slide = null;
204 if (previous) {
205 slide = this.selectedSlide.parentNode.previousSibling;
206 if (slide && slide.nodeType==3)
207 slide = slide.previousSibling;
208 }
209 else {
210 slide = this.selectedSlide.parentNode.nextSibling;
211 if (slide && slide.nodeType==3)
212 slide = slide.nextSibling;
213 }
214
215 if (!slide)
216 return;
217 else {
218 var target = slide.getElementsByTagName('a')[0];
219 raiseMouseEvent(target, 'click');
220 var index = parseInt(target.getAttribute('portfolio:position'));
221 this.centerSlide(index);
222 }
223 };
224
225 FilmSlider.prototype.addEventListeners = function() {
226 var thisSlider = this;
227 addListener(window, 'resize', function(evt){thisSlider.resizeSlider(evt);});
228 addListener(this.filmBar, 'click', function(evt){thisSlider.thumbnailClickHandler(evt);});
229 addListener(this.toolbar, 'click', function(evt){thisSlider.toolbarClickHandler(evt);});
230 addListener(window, 'load', function(evt){thisSlider.fitToScreen(evt);});
231
232 // dd listeners
233 addListener(this.slider, 'mousedown', this.ddHandlers['down']);
234 if(browser.isDOM2Event){
235 if (browser.isAppleWebKit) {
236 this.filmBar.addEventListener('mousewheel', function(evt){thisSlider.mouseWheelHandler(evt);}, false);
237 }
238 else {
239 addListener(this.filmBar, 'DOMMouseScroll', function(evt){thisSlider.mouseWheelHandler(evt);});
240 }
241 }
242 else if (browser.isIE6up) {
243 addListener(this.filmBar, 'mousewheel', function(evt){thisSlider.mouseWheelHandler(evt);});
244 }
245
246 addListener(document, 'keydown', function(evt){thisSlider.keyDownHandler(evt);});
247 addListener(document, 'keypress', function(evt){thisSlider.keyPressHandler(evt);});
248 };
249
250
251 FilmSlider.prototype.mouseDownHandler = function(evt) {
252 this.initialClickPoint = new Point(evt.clientX, evt.clientY);
253 this.initialPosition = this.getSliderPosition();
254 this.dragInProgress = true;
255 addListener(document, 'mousemove', this.ddHandlers['move']);
256 addListener(document, 'mouseup', this.ddHandlers['up']);
257 addListener(document.body, 'mouseout', this.ddHandlers['out'])
258
259 };
260
261
262 FilmSlider.prototype.mouseMoveHandler = function(evt) {
263 if(!this.dragInProgress)
264 return;
265
266 clearSelection();
267 evt = getEventObject(evt);
268 var currentPoint = new Point(evt.clientX, evt.clientY);
269 var displacement = currentPoint.diff(this.initialClickPoint);
270 this.setSliderPosition(this.initialPosition.add(displacement));
271 };
272
273 FilmSlider.prototype.mouseUpHandler = function(evt) {
274 this.dragInProgress = false;
275 evt = getEventObject(evt);
276 this.mouseMoveHandler(evt);
277 };
278
279
280 FilmSlider.prototype.mouseOutHandler = function(evt) {
281 evt = getEventObject(evt);
282 var x = evt.clientX;
283 var y = evt.clientY;
284 if (x < 0 ||
285 x > this.winSize['width'] ||
286 y < 0 ||
287 y > this.winSize['height']
288 ){
289 this.mouseUpHandler(evt);
290 }
291 };
292
293 FilmSlider.prototype.thumbnailClickHandler = function(evt) {
294 var target = getTargetedObject(evt);
295 while (target.tagName != 'A' && target != this.filmBar)
296 target = target.parentNode;
297 if (target.tagName != 'A')
298 return;
299 else {
300 if (this.viewMode == 'full') {
301 this.mosaique.unload();
302 this.mosaique = null;
303 this.viewMode = 'medium';
304 }
305 disableDefault(evt);
306 disablePropagation(evt);
307 target.blur();
308
309 var imgBaseUrl = target.href;
310 var canonicalImgUrl;
311 if (this.ctxUrlTranslation[0])
312 canonicalImgUrl = imgBaseUrl.replace(this.ctxUrlTranslation[0],
313 this.ctxUrlTranslation[1]);
314 else
315 canonicalImgUrl = imgBaseUrl;
316
317 var ajaxUrl = imgBaseUrl + '/photo_view_ajax';
318 var thisFS = this;
319
320 //this.pendingImage.src = canonicalImgUrl + '/getResizedImage?size=600';
321 var thumbnail = target.getElementsByTagName('IMG')[0];
322 var bestFitSize = this.getBestFitSize(thumbnail.height/thumbnail.width);
323 this.pendingImage.src = canonicalImgUrl + '/getResizedImage?size=' + bestFitSize;
324
325 // update buttons
326 var fullScreenLink = this.buttons['full_screen'].parentNode;
327 fullScreenLink.href = canonicalImgUrl + '/zoom_view';
328
329 var toggleSelectionBtn = this.buttons['toggle_selection'];
330 var toggleSelectionLink = toggleSelectionBtn.parentNode;
331 this.selectedSlideInSelection = (target.className=='selected');
332 if (this.selectedSlideInSelection) {
333 toggleSelectionBtn.src = portal_url() + '/unselect_flag_btn.gif';
334 toggleSelectionBtn.alt = toggleSelectionLink.title = 'Retirer de la sélection';
335 toggleSelectionLink.href = canonicalImgUrl + '/remove_to_selection';
336 }
337 else {
338 toggleSelectionBtn.src = portal_url() + '/select_flag_btn.gif';
339 toggleSelectionBtn.alt = toggleSelectionLink.title = 'Ajouter à la sélection';
340 toggleSelectionLink.href = canonicalImgUrl + '/add_to_selection';
341 }
342
343 var showBuyableButtonLink = this.buttons['show_buyable'].parentNode;
344 showBuyableButtonLink.href = canonicalImgUrl + '/get_slide_buyable_items';
345 this.cartSlide.innerHTML = '';
346 this.cartSlide.style.visibility='hidden';
347
348
349 var metadataButton = this.buttons['edit_metadata']
350 if (metadataButton) {
351 var metadataEditLink = metadataButton.parentNode;
352 metadataEditLink.href = canonicalImgUrl + '/photo_edit_form'
353 }
354
355
356 var req = new XMLHttpRequest();
357 req.onreadystatechange = function() {
358 switch (req.readyState) {
359 case 1 :
360 showProgressImage();
361 break;
362 case 2 :
363 try {
364 if (! isTextMime.exec(req.getResponseHeader('Content-Type'))) {
365 req.onreadystatechange = null;
366 req.abort();
367 hideProgressImage();
368 window.location.href = thisFS._fallBackUrl;
369 }
370 }
371 catch(e){}
372 break;
373 case 4 :
374 hideProgressImage();
375 if (req.status == '200')
376 thisFS.populateViewer(req);
377 else
378 //window.location.href = target.href;
379 console.error(ajaxUrl);
380
381 };
382 };
383
384 req.open("GET", ajaxUrl, true);
385 req.send(null);
386
387 // update old displayed slide className
388 var className = this.selectedSlide.className;
389 var classes = className.split(' ');
390 var newClasses = new Array();
391 var name;
392
393 for (i in classes) {
394 name = classes[i];
395 if (name == 'displayed')
396 continue;
397 else
398 newClasses.push(name);
399 }
400
401 this.selectedSlide.className = newClasses.join(' ')
402
403 // hightlight new displayed slide
404 this.selectedSlide = target;
405 className = this.selectedSlide.className;
406 classes = className.split(' ');
407 classes.push('displayed');
408 this.selectedSlide.className = classes.join(' ');
409 }
410 };
411
412 FilmSlider.prototype.toolbarClickHandler = function(evt) {
413 var target = getTargetedObject(evt);
414 if(target.tagName == 'IMG' && target.getAttribute('name')){
415 switch(target.getAttribute('name')) {
416 case 'previous' :
417 disableDefault(evt);
418 disablePropagation(evt);
419 var button = target;
420 var link = button.parentNode;
421 link.blur();
422 this.loadSibling(true);
423 break;
424 case 'next' :
425 disableDefault(evt);
426 disablePropagation(evt);
427 var button = target;
428 var link = button.parentNode;
429 link.blur();
430 this.loadSibling(false);
431 break;
432 case 'full_screen':
433 disableDefault(evt);
434 disablePropagation(evt);
435 target.parentNode.blur();
436 if (this.viewMode == 'full') {
437 this.mosaique.unload();
438 this.mosaique = null;
439 this.viewMode = 'medium';
440 return;
441 }
442 var main = document.getElementById('photo_viewer');
443 var url = target.parentNode.href;
444 url = url.substring(0, url.length - '/zoom_view'.length);
445 var margins = {'top':0, 'right':-1, 'bottom':0, 'left':0};
446 this.mosaique = new Mosaique(main, url, margins);
447 this.viewMode = 'full';
448 break;
449
450 case 'toggle_selection':
451 disableDefault(evt);
452 disablePropagation(evt);
453 var button = target;
454 var link = button.parentNode;
455 link.blur();
456
457 var req = new XMLHttpRequest();
458 var url = link.href;
459 req.open("POST", url, true);
460 req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
461 req.send("ajax=1");
462
463 // toggle button
464 var parts = url.split('/');
465 var canonicalImgUrl = parts.slice(0, parts.length-1).join('/');
466
467 if (isAddToSelection.test(url)) {
468 button.src = portal_url() + '/unselect_flag_btn.gif';
469 button.alt = link.title = 'Retirer de la sélection';
470 link.href = canonicalImgUrl + '/remove_to_selection';
471 this.selectedSlide.className = 'selected displayed';
472 this.image.parentNode.className = 'selected';
473 this.selectedSlideInSelection = true;
474 }
475 else {
476 button.src = portal_url() + '/select_flag_btn.gif';
477 button.alt = link.title = 'Ajouter à la sélection';
478 link.href = canonicalImgUrl + '/add_to_selection';
479 this.selectedSlide.className = 'displayed';
480 this.image.parentNode.className = '';
481 this.selectedSlideInSelection = false;
482 }
483 break;
484
485 case 'show_buyable':
486 disableDefault(evt);
487 disablePropagation(evt);
488 var button = target;
489 var link = button.parentNode;
490 link.blur();
491 var slide = this.cartSlide;
492 slide.innerHTML = '';
493 slide.style.visibility = 'visible';
494 var cw = new CartWidget(slide, link.href);
495 cw.onCancel = function() {
496 CartWidget.prototype.onCancel.apply(this);
497 slide.style.visibility = 'hidden';
498 };
499 cw.onAfterConfirm = function() {
500 slide.style.visibility = 'hidden';
501 };
502 break;
503
504
505
506
507 /*
508 case 'edit_metadata' :
509 disableDefault(evt);
510 disablePropagation(evt);
511 target.blur();
512 if (this.viewMode == 'full') {
513 this.mosaique.unload();
514 this.mosaique = null;
515 this.viewMode = 'medium';
516 return;
517 }
518 var fi = new FragmentImporter(absolute_url());
519 fi.useMacro('metadata_edit_form_macros', 'iptc', 'image_metadata');
520 break;
521 */
522 }
523 }
524 };
525
526
527 if(browser.isDOM2Event){
528 if (browser.isAppleWebKit) {
529 FilmSlider.prototype.mouseWheelHandler = function(evt) {
530 disableDefault(evt);
531 var pos = this.getSliderPosition();
532 pos.x -= evt.wheelDelta / 40;
533 this.setSliderPosition(pos);
534 };
535 }
536 else {
537 FilmSlider.prototype.mouseWheelHandler = function(evt) {
538 disableDefault(evt);
539 var pos = this.getSliderPosition();
540 pos.x += evt.detail * 3;
541 this.setSliderPosition(pos);
542 };
543 }
544 }
545 else if (browser.isIE6up) {
546 FilmSlider.prototype.mouseWheelHandler = function() {
547 var evt = window.event;
548 evt.returnValue = false;
549 var pos = this.getSliderPosition();
550 pos.x -= evt.wheelDelta / 40;
551 this.setSliderPosition(pos);
552 };
553 }
554
555 FilmSlider.prototype.keyDownHandler = function(evt) {
556 var evt = getEventObject(evt);
557 switch (evt.keyCode) {
558 case keyLeft :
559 this.loadSibling(true);
560 break;
561 case keyRight :
562 this.loadSibling(false);
563 break;
564 default:
565 return;
566 }
567 };
568
569
570 FilmSlider.prototype.keyPressHandler = function(evt) {
571 var target = getTargetedObject(evt);
572 if (target.tagName == 'INPUT' || target.tagName== 'TEXTAREA')
573 return;
574 var evt = getEventObject(evt);
575 var charPress = String.fromCharCode((evt.keyCode) ? evt.keyCode : evt.which);
576 switch(charPress) {
577 case 'f':
578 case 'F':
579 raiseMouseEvent(this.buttons['full_screen'], 'click');
580 break;
581 }
582 };
583
584 FilmSlider.prototype.populateViewer = function(req) {
585 var elements = req.responseXML.documentElement.childNodes;
586 for(var i=0 ; i < elements.length ; i++ ) {
587 element = elements[i];
588 switch (element.nodeName) {
589 case 'fragment' :
590 var dest = document.getElementById(element.getAttribute('id'));
591 dest.innerHTML = element.firstChild.nodeValue;
592 break;
593 case 'imageattributes' :
594 var link = this.buttons['back_to_portfolio'].parentNode;
595 link.href = element.getAttribute('backToContextUrl');
596 link = this.buttons['show_buyable'].parentNode;
597 var buyable = element.getAttribute('buyable');
598 if(buyable == 'True')
599 link.className = null;
600 else if(buyable == 'False')
601 link.className = 'hidden';
602 this.image.alt = element.getAttribute('alt');
603 this.lastBCElement.href = element.getAttribute('lastBcUrl');
604 this.lastBCElement.innerHTML = element.getAttribute('img_id');
605 break;
606 }
607 }
608 };
609
610 FilmSlider.prototype.refreshImage = function() {
611 this.image.style.visibility = 'hidden';
612 this.image.src = this.pendingImage.src;
613 this.image.width = this.pendingImage.width;
614 this.image.height = this.pendingImage.height;
615 this.image.style.visibility = 'visible';
616 if (this.selectedSlideInSelection)
617 this.image.parentNode.className = 'selected';
618 else
619 this.image.parentNode.className = '';
620 };
621
622 FilmSlider.prototype.startSlideShow = function() {
623 this.slideShowSlide = this.pendingSlideShowSlide = this.selectedSlide;
624 return this.slideShowSlide.href;
625 };
626
627 FilmSlider.prototype.slideShowNext = function() {
628 var nextSlide = this.slideShowSlide.parentNode.nextSibling;
629 if (nextSlide && nextSlide.nodeType==3)
630 nextSlide = nextSlide.nextSibling;
631
632 if (nextSlide) {
633 nextSlide = nextSlide.getElementsByTagName('a')[0];
634 this.pendingSlideShowSlide = nextSlide;
635 return this.pendingSlideShowSlide.href;
636 }
637 else {
638 var row = this.slideShowSlide.parentNode.parentNode;
639 var first = row.firstChild;
640 if (first.nodeType==3)
641 first = first.nextSibling;
642 this.pendingSlideShowSlide = first.getElementsByTagName('a')[0];
643 return this.pendingSlideShowSlide.href;
644 }
645 };
646
647 FilmSlider.prototype.slideShowPrevious = function() {
648 var previousSlide = this.slideShowSlide.parentNode.previousSibling;
649 if (previousSlide && previousSlide.nodeType==3)
650 previousSlide = previousSlide.previousSibling;
651
652 if (previousSlide) {
653 previousSlide = previousSlide.getElementsByTagName('a')[0];
654 this.pendingSlideShowSlide = previousSlide;
655 return this.pendingSlideShowSlide.href;
656 }
657 else {
658 var row = this.slideShowSlide.parentNode.parentNode;
659 var last = row.lastChild;
660 if (last.nodeType==3)
661 last = last.previousSibling;
662 this.pendingSlideShowSlide = last.getElementsByTagName('a')[0];
663 return this.pendingSlideShowSlide.href;
664 }
665 };
666
667 FilmSlider.prototype.slideShowImageLoaded = function() {
668 this.slideShowSlide = this.pendingSlideShowSlide;
669 };
670
671 FilmSlider.prototype.stopSlideShow = function() {
672 raiseMouseEvent(this.slideShowSlide, 'click');
673 var index = parseInt(this.selectedSlide.getAttribute('portfolio:position'));
674 this.centerSlide(index);
675 };
676
677
678 /* UTILS */
679 function Point(x, y) {
680 this.x = Math.round(x);
681 this.y = Math.round(y);
682 }
683 Point.prototype.diff = function(point) { return new Point(this.x - point.x, this.y - point.y); };
684 Point.prototype.add = function(point) { return new Point(this.x + point.x, this.y + point.y); };
685 Point.prototype.mul = function(k) { return new Point(this.x * k, this.y *k)};
686 Point.prototype.toString = function() { return "(" + String(this.x) + ", " + String(this.y) + ")"; };
687
688 })();