Version initiale.
[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 * $Id: photo_film_viewer.js 1303 2009-08-21 22:19:05Z pin $
6 * $URL: http://svn.luxia.fr/svn/labo/projects/zope/Portfolio/trunk/skins/photo_film_viewer.js $
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 if (sliderRatio < 1) {
96 this.rail.style.width = filmBarWidth + 'px';
97 this.slider.style.width = Math.round(sliderWidth) + 'px';
98 this.rail.style.display = 'block';
99 this.rail.style.visibility = 'visible';
100 }
101 else {
102 this.rail.style.display = 'none';
103 this.rail.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 ch = document.body.clientHeight;
126 var sh = getObjectHeight(this.stretchable);
127 var newSize = sh + wh - ch;
128 this.stretchable.style.height = newSize + 'px';
129
130 var ratio = this.image.height / this.image.width;
131 var bestFitSize = this.getBestFitSize(ratio);
132 var currentSize = parseInt(imgRequestedSize.exec(this.image.src)[1]);
133 if (currentSize != bestFitSize) {
134 var src = this.image.src.replace(imgRequestedSize, 'size=' + bestFitSize);
135 this.pendingImage.src = src;
136 }
137 };
138
139 FilmSlider.prototype.getBestFitSize = function(ratio) {
140 var fw = getObjectWidth(this.stretchable) - 1;
141 var fh = getObjectHeight(this.stretchable) - 1;
142
143 var i, irw, irh;
144 if (ratio < 1) {
145 for (i=DEFAULT_IMAGE_SIZES.length -1 ; i>0 ; i--) {
146 irw = DEFAULT_IMAGE_SIZES[i];
147 irh = irw * ratio;
148 if (irw <= fw && irh <= fh)
149 break;
150 }
151 }
152 else {
153 for (i=DEFAULT_IMAGE_SIZES.length -1 ; i>0 ; i--) {
154 irh = DEFAULT_IMAGE_SIZES[i];
155 irw = irh / ratio;
156 if (irw <= fw && irh <= fh)
157 break;
158 }
159 }
160 return DEFAULT_IMAGE_SIZES[i];
161 };
162
163 FilmSlider.prototype.centerSlide = function(slideIndex) {
164 if (this.sliderRatio > 1)
165 return;
166 var filmBarWidth = getObjectWidth(this.filmBar);
167 var x = slideIndex * this.slideSize
168 x = x - (filmBarWidth - this.slideSize) / 2.0;
169 x = x * this.sliderSpeedRatio;
170 var p = new Point( -x, 0 )
171 this.setSliderPosition(p);
172 };
173
174 FilmSlider.prototype.setSliderPosition = function(point) {
175 if(point.x < 0)
176 point.x = 0;
177 if (point.x > this.maxRightPosition)
178 point.x = this.maxRightPosition;
179 this.slider.style.left = point.x + 'px';
180 this.setFilmPosition(point);
181 };
182
183 FilmSlider.prototype.setFilmPosition = function(point) {
184 this.film.style.left = point.x / this.sliderSpeedRatio + 'px';
185 };
186
187 FilmSlider.prototype.getSliderPosition = function() {
188 var x = parseInt(this.slider.style.left);
189 var y = parseInt(this.slider.style.top);
190 var p = new Point(x, y);
191 return p;
192 };
193
194 FilmSlider.prototype.getFilmPosition = function() {
195 var x = parseInt(this.film.style.left);
196 var y = parseInt(this.film.style.top);
197 var p = new Point(x, y);
198 return p;
199 };
200
201 FilmSlider.prototype.loadSibling = function(previous) {
202 var slide = null;
203 if (previous) {
204 slide = this.selectedSlide.parentNode.previousSibling;
205 if (slide && slide.nodeType==3)
206 slide = slide.previousSibling;
207 }
208 else {
209 slide = this.selectedSlide.parentNode.nextSibling;
210 if (slide && slide.nodeType==3)
211 slide = slide.nextSibling;
212 }
213
214 if (!slide)
215 return;
216 else {
217 var target = slide.getElementsByTagName('a')[0];
218 raiseMouseEvent(target, 'click');
219 var index = parseInt(target.getAttribute('portfolio:position'));
220 this.centerSlide(index);
221 }
222 };
223
224 FilmSlider.prototype.addEventListeners = function() {
225 var thisSlider = this;
226 addListener(window, 'resize', function(evt){thisSlider.resizeSlider(evt);});
227 addListener(this.filmBar, 'click', function(evt){thisSlider.thumbnailClickHandler(evt);});
228 addListener(this.toolbar, 'click', function(evt){thisSlider.toolbarClickHandler(evt);});
229 addListener(window, 'load', function(evt){thisSlider.fitToScreen(evt);});
230
231 // dd listeners
232 addListener(this.slider, 'mousedown', this.ddHandlers['down']);
233 if(browser.isDOM2Event){
234 if (browser.isAppleWebKit) {
235 this.filmBar.addEventListener('mousewheel', function(evt){thisSlider.mouseWheelHandler(evt);}, false);
236 }
237 else {
238 addListener(this.filmBar, 'DOMMouseScroll', function(evt){thisSlider.mouseWheelHandler(evt);});
239 }
240 }
241 else if (browser.isIE6up) {
242 addListener(this.filmBar, 'mousewheel', function(evt){thisSlider.mouseWheelHandler(evt);});
243 }
244
245 addListener(document, 'keydown', function(evt){thisSlider.keyDownHandler(evt);});
246 addListener(document, 'keypress', function(evt){thisSlider.keyPressHandler(evt);});
247 };
248
249
250 FilmSlider.prototype.mouseDownHandler = function(evt) {
251 this.initialClickPoint = new Point(evt.clientX, evt.clientY);
252 this.initialPosition = this.getSliderPosition();
253 this.dragInProgress = true;
254 addListener(document, 'mousemove', this.ddHandlers['move']);
255 addListener(document, 'mouseup', this.ddHandlers['up']);
256 addListener(document.body, 'mouseout', this.ddHandlers['out'])
257
258 };
259
260
261 FilmSlider.prototype.mouseMoveHandler = function(evt) {
262 if(!this.dragInProgress)
263 return;
264
265 clearSelection();
266 evt = getEventObject(evt);
267 var currentPoint = new Point(evt.clientX, evt.clientY);
268 var displacement = currentPoint.diff(this.initialClickPoint);
269 this.setSliderPosition(this.initialPosition.add(displacement));
270 };
271
272 FilmSlider.prototype.mouseUpHandler = function(evt) {
273 this.dragInProgress = false;
274 evt = getEventObject(evt);
275 this.mouseMoveHandler(evt);
276 };
277
278
279 FilmSlider.prototype.mouseOutHandler = function(evt) {
280 evt = getEventObject(evt);
281 var x = evt.clientX;
282 var y = evt.clientY;
283 if (x < 0 ||
284 x > this.winSize['width'] ||
285 y < 0 ||
286 y > this.winSize['height']
287 ){
288 this.mouseUpHandler(evt);
289 }
290 };
291
292 FilmSlider.prototype.thumbnailClickHandler = function(evt) {
293 var target = getTargetedObject(evt);
294 while (target.tagName != 'A' && target != this.filmBar)
295 target = target.parentNode;
296 if (target.tagName != 'A')
297 return;
298 else {
299 if (this.viewMode == 'full') {
300 this.mosaique.unload();
301 this.mosaique = null;
302 this.viewMode = 'medium';
303 }
304 disableDefault(evt);
305 disablePropagation(evt);
306 target.blur();
307
308 var imgBaseUrl = target.href;
309 var canonicalImgUrl;
310 if (this.ctxUrlTranslation[0])
311 canonicalImgUrl = imgBaseUrl.replace(this.ctxUrlTranslation[0],
312 this.ctxUrlTranslation[1]);
313 else
314 canonicalImgUrl = imgBaseUrl;
315
316 var ajaxUrl = imgBaseUrl + '/photo_view_ajax';
317 var thisFS = this;
318
319 //this.pendingImage.src = canonicalImgUrl + '/getResizedImage?size=600';
320 var thumbnail = target.getElementsByTagName('IMG')[0];
321 var bestFitSize = this.getBestFitSize(thumbnail.height/thumbnail.width);
322 this.pendingImage.src = canonicalImgUrl + '/getResizedImage?size=' + bestFitSize;
323
324 // update buttons
325 var fullScreenLink = this.buttons['full_screen'].parentNode;
326 fullScreenLink.href = canonicalImgUrl + '/zoom_view';
327
328 var toggleSelectionBtn = this.buttons['toggle_selection'];
329 var toggleSelectionLink = toggleSelectionBtn.parentNode;
330 this.selectedSlideInSelection = (target.className=='selected');
331 if (this.selectedSlideInSelection) {
332 toggleSelectionBtn.src = portal_url() + '/unselect_flag_btn.gif';
333 toggleSelectionBtn.alt = toggleSelectionLink.title = 'Retirer de la sélection';
334 toggleSelectionLink.href = canonicalImgUrl + '/remove_to_selection';
335 }
336 else {
337 toggleSelectionBtn.src = portal_url() + '/select_flag_btn.gif';
338 toggleSelectionBtn.alt = toggleSelectionLink.title = 'Ajouter à la sélection';
339 toggleSelectionLink.href = canonicalImgUrl + '/add_to_selection';
340 }
341
342 var showBuyableButtonLink = this.buttons['show_buyable'].parentNode;
343 showBuyableButtonLink.href = canonicalImgUrl + '/get_slide_buyable_items';
344 this.cartSlide.innerHTML = '';
345 this.cartSlide.style.visibility='hidden';
346
347
348 var metadataButton = this.buttons['edit_metadata']
349 if (metadataButton) {
350 var metadataEditLink = metadataButton.parentNode;
351 metadataEditLink.href = canonicalImgUrl + '/photo_edit_form'
352 }
353
354
355 var req = new XMLHttpRequest();
356 req.onreadystatechange = function() {
357 switch (req.readyState) {
358 case 1 :
359 showProgressImage();
360 break;
361 case 2 :
362 try {
363 if (! isTextMime.exec(req.getResponseHeader('Content-Type'))) {
364 req.onreadystatechange = null;
365 req.abort();
366 hideProgressImage();
367 window.location.href = thisFS._fallBackUrl;
368 }
369 }
370 catch(e){}
371 break;
372 case 4 :
373 hideProgressImage();
374 if (req.status == '200')
375 thisFS.populateViewer(req);
376 else
377 //window.location.href = target.href;
378 console.error(ajaxUrl);
379
380 };
381 };
382
383 req.open("GET", ajaxUrl, true);
384 req.send(null);
385
386 // update old displayed slide className
387 var className = this.selectedSlide.className;
388 var classes = className.split(' ');
389 var newClasses = new Array();
390 var name;
391
392 for (i in classes) {
393 name = classes[i];
394 if (name == 'displayed')
395 continue;
396 else
397 newClasses.push(name);
398 }
399
400 this.selectedSlide.className = newClasses.join(' ')
401
402 // hightlight new displayed slide
403 this.selectedSlide = target;
404 className = this.selectedSlide.className;
405 classes = className.split(' ');
406 classes.push('displayed');
407 this.selectedSlide.className = classes.join(' ');
408 }
409 };
410
411 FilmSlider.prototype.toolbarClickHandler = function(evt) {
412 var target = getTargetedObject(evt);
413 if(target.tagName == 'IMG' && target.getAttribute('name')){
414 switch(target.getAttribute('name')) {
415 case 'previous' :
416 disableDefault(evt);
417 disablePropagation(evt);
418 var button = target;
419 var link = button.parentNode;
420 link.blur();
421 this.loadSibling(true);
422 break;
423 case 'next' :
424 disableDefault(evt);
425 disablePropagation(evt);
426 var button = target;
427 var link = button.parentNode;
428 link.blur();
429 this.loadSibling(false);
430 break;
431 case 'full_screen':
432 disableDefault(evt);
433 disablePropagation(evt);
434 target.parentNode.blur();
435 if (this.viewMode == 'full') {
436 this.mosaique.unload();
437 this.mosaique = null;
438 this.viewMode = 'medium';
439 return;
440 }
441 var main = document.getElementById('photo_viewer');
442 var url = target.parentNode.href;
443 url = url.substring(0, url.length - '/zoom_view'.length);
444 var margins = {'top':0, 'right':-1, 'bottom':0, 'left':0};
445 this.mosaique = new Mosaique(main, url, margins);
446 this.viewMode = 'full';
447 break;
448
449 case 'toggle_selection':
450 disableDefault(evt);
451 disablePropagation(evt);
452 var button = target;
453 var link = button.parentNode;
454 link.blur();
455
456 var req = new XMLHttpRequest();
457 var url = link.href;
458 req.open("POST", url, true);
459 req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
460 req.send("ajax=1");
461
462 // toggle button
463 var parts = url.split('/');
464 var canonicalImgUrl = parts.slice(0, parts.length-1).join('/');
465
466 if (isAddToSelection.test(url)) {
467 button.src = portal_url() + '/unselect_flag_btn.gif';
468 button.alt = link.title = 'Retirer de la sélection';
469 link.href = canonicalImgUrl + '/remove_to_selection';
470 this.selectedSlide.className = 'selected displayed';
471 this.image.parentNode.className = 'selected';
472 this.selectedSlideInSelection = true;
473 }
474 else {
475 button.src = portal_url() + '/select_flag_btn.gif';
476 button.alt = link.title = 'Ajouter à la sélection';
477 link.href = canonicalImgUrl + '/add_to_selection';
478 this.selectedSlide.className = 'displayed';
479 this.image.parentNode.className = '';
480 this.selectedSlideInSelection = false;
481 }
482 break;
483
484 case 'show_buyable':
485 disableDefault(evt);
486 disablePropagation(evt);
487 var button = target;
488 var link = button.parentNode;
489 link.blur();
490 var slide = this.cartSlide;
491 slide.innerHTML = '';
492 slide.style.visibility = 'visible';
493 var cw = new CartWidget(slide, link.href);
494 cw.onCancel = function() {
495 CartWidget.prototype.onCancel.apply(this);
496 slide.style.visibility = 'hidden';
497 };
498 cw.onAfterConfirm = function() {
499 slide.style.visibility = 'hidden';
500 };
501 break;
502
503
504
505
506 /*
507 case 'edit_metadata' :
508 disableDefault(evt);
509 disablePropagation(evt);
510 target.blur();
511 if (this.viewMode == 'full') {
512 this.mosaique.unload();
513 this.mosaique = null;
514 this.viewMode = 'medium';
515 return;
516 }
517 var fi = new FragmentImporter(absolute_url());
518 fi.useMacro('metadata_edit_form_macros', 'iptc', 'image_metadata');
519 break;
520 */
521 }
522 }
523 };
524
525
526 if(browser.isDOM2Event){
527 if (browser.isAppleWebKit) {
528 FilmSlider.prototype.mouseWheelHandler = function(evt) {
529 disableDefault(evt);
530 var pos = this.getSliderPosition();
531 pos.x -= evt.wheelDelta / 40;
532 this.setSliderPosition(pos);
533 };
534 }
535 else {
536 FilmSlider.prototype.mouseWheelHandler = function(evt) {
537 disableDefault(evt);
538 var pos = this.getSliderPosition();
539 pos.x += evt.detail * 3;
540 this.setSliderPosition(pos);
541 };
542 }
543 }
544 else if (browser.isIE6up) {
545 FilmSlider.prototype.mouseWheelHandler = function() {
546 var evt = window.event;
547 evt.returnValue = false;
548 var pos = this.getSliderPosition();
549 pos.x -= evt.wheelDelta / 40;
550 this.setSliderPosition(pos);
551 };
552 }
553
554 FilmSlider.prototype.keyDownHandler = function(evt) {
555 var evt = getEventObject(evt);
556 switch (evt.keyCode) {
557 case keyLeft :
558 this.loadSibling(true);
559 break;
560 case keyRight :
561 this.loadSibling(false);
562 break;
563 default:
564 return;
565 }
566 };
567
568
569 FilmSlider.prototype.keyPressHandler = function(evt) {
570 var target = getTargetedObject(evt);
571 if (target.tagName == 'INPUT' || target.tagName== 'TEXTAREA')
572 return;
573 var evt = evt = getEventObject(evt);
574 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.selectedSlide;
624 this.nextSlideShowSlide = this.selectedSlide;
625 return this.slideShowSlide.href;
626 };
627
628 FilmSlider.prototype.slideShowNext = function() {
629 this.slideShowSlide = this.nextSlideShowSlide;
630 var nextSlide = this.slideShowSlide.parentNode.nextSibling;
631 if (nextSlide && nextSlide.nodeType==3)
632 nextSlide = nextSlide.nextSibling;
633
634 if (nextSlide) {
635 nextSlide = nextSlide.getElementsByTagName('a')[0];
636 this.nextSlideShowSlide = nextSlide;
637 return nextSlide.href;
638 }
639 else {
640 var row = this.slideShowSlide.parentNode.parentNode;
641 var first = row.firstChild;
642 if (first.nodeType==3)
643 first = first.nextSibling;
644 this.nextSlideShowSlide = first.getElementsByTagName('a')[0];
645 return this.nextSlideShowSlide.href;
646 }
647 };
648
649 FilmSlider.prototype.stopSlideShow = function() {
650 raiseMouseEvent(this.slideShowSlide, 'click');
651 var index = parseInt(this.selectedSlide.getAttribute('portfolio:position'));
652 this.centerSlide(index);
653 };
654
655
656 /* UTILS */
657 function Point(x, y) {
658 this.x = Math.round(x);
659 this.y = Math.round(y);
660 }
661 Point.prototype.diff = function(point) { return new Point(this.x - point.x, this.y - point.y); };
662 Point.prototype.add = function(point) { return new Point(this.x + point.x, this.y + point.y); };
663 Point.prototype.mul = function(k) { return new Point(this.x * k, this.y *k)};
664 Point.prototype.toString = function() { return "(" + String(this.x) + ", " + String(this.y) + ")"; };
665
666 })();