source: Dev/trunk/src/client/dojox/mobile/pageTurningUtils.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 18.3 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/array",
4        "dojo/_base/connect",
5        "dojo/_base/event",
6        "dojo/dom-class",
7        "dojo/dom-construct",
8        "dojo/dom-style",
9        "./_css3"
10], function(kernel, array, connect, event, domClass, domConstruct, domStyle, css3){
11        // module:
12        //              dojox/mobile/pageTurningUtils
13       
14        kernel.experimental("dojox.mobile.pageTurningUtils");
15
16        return function(){
17                // summary:
18                //              Utilities to provide page turning effects just like turning a real book.
19                // example:
20                // |    require([
21                // |            "dojo/ready",
22                // |            "dojox/mobile/pageTurningUtils"
23                // |    ], function(ready, pageTurningUtils){
24                // |            var utils = new pageTurningUtils();
25                // |            ready(function(){
26                // |                    utils.init(300, 400); // Specify width and height by pixels
27                // |                    utils.initCatalog(document.getElementById("catalog"));
28                // |            });
29                // |    );
30                // |    <div id="catalog">
31                // |            <div id="page1">
32                // |                    <div id="front1"><img src="img1.png"></div>
33                // |                    <div id="back1"><img src="img2.png"></div>
34                // |            </div>
35                // |            <div id="page2">
36                // |                    <div id="front2"><img src="img3.png"></div>
37                // |                    <div id="back2"><img src="img4.png"></div>
38                // |            </div>
39                // |            <div id="page3">
40                // |                    <div id="front3"><img src="img5.png"></div>
41                // |                    <div id="back3"></div>
42                // |            </div>
43                // |    </div>
44
45                this.w = 0;
46                this.h = 0;
47                this.turnfrom = "top";
48                this.page = 1;
49                this.dogear = 1.0;
50                this.duration = 2;
51                this.alwaysDogeared = false;
52
53                /* Internal properties */
54                this._styleParams = {};
55                this._catalogNode = null;
56                this._currentPageNode = null;
57                this._transitionEndHandle = null;
58
59                this.init = function(/*int*/w, /*int*/h, /*String?*/turnfrom,
60                                                        /*int?*/page, /*Number?*/dogear, /*Number?*/duration,
61                                                        /*Boolean?*/alwaysDogeared){
62                        // summary:
63                        //              Sets property values necessary for calculating the style parameters
64                        //              for page positioning and page turning effects.
65                        // w: int
66                        //              The width of each page by pixels. You cannot specify it by percentage.
67                        // h: int
68                        //              The height of each page by pixels. You cannot specify it by percentage.
69                        // turnfrom: String?
70                        //              Specifies from which side/corner the page turning starts.
71                        //              You can choose from "top", "bottom" or "left". Defaults to "top".
72                        //              If "top", each page is turned from top-right corner of the page.
73                        //              If "bottom", each page is turned from bottom-right corner of the page.
74                        //              And if "left", each page is turned from top-left corner of the page.
75                        //              The page is shown as dog-eared except the case of "bottom".
76                        // page: int?
77                        //              The number of pages shown in the screen at a time.
78                        //              This parameter should be either of 1 or 2. Defaults to 1.
79                        //              If 1, the only one side of two facing pages are shown.
80                        //              If 2, the two facing pages are shown at a time.
81                        // dogear: Number?
82                        //              The ratio of actual dog-ear width to the maximum dog-ear width which is
83                        //              11 percent of the page width (= 0.11 * w).
84                        //              This parameter should be a float number between 0 and 1. Defaults to 1.
85                        //              The actual dog-ear width is calculated by the following formula:
86                        // |            0.11 * w * dogear.
87                        //              This parameter is ignored if "bottom" is specified to turnfrom parameter.
88                        // duration: Number?
89                        //              The duration of page turning animations by seconds. (ex. 1.5, 3, etc)
90                        //              Defaults to 2.
91                        // alwaysDogeared: Boolean?
92                        //              Specifies whether all pages are always dog-eared or not.
93                        //              If true, all pages are always dog-eared.
94                        //              If false, only the current page is dog-eared while the others are not.
95                        //              This parameter is ignored if "bottom" is specified to turnfrom parameter.
96                       
97                        // Set property values
98                        this.w = w;
99                        this.h = h;
100                        this.turnfrom = turnfrom ? turnfrom : this.turnfrom;
101                        this.page = page ? page : this.page;
102                        this.dogear = typeof dogear !== 'undefined' ? dogear : this.dogear;
103                        this.duration = typeof duration !== 'undefined' ? duration : this.duration;
104                        this.alwaysDogeared = typeof alwaysDogeared !== 'undefined' ? alwaysDogeared : this.alwaysDogeared
105                       
106                        if(this.turnfrom === "bottom"){ // dog-ear is not supported if using "bottom"
107                                this.alwaysDogeared = true;
108                        }
109                        // Calculate style parameters for page positioning and page turning effects
110                        this._calcStyleParams();
111                };
112
113                this._calcStyleParams = function(){
114                        // tags:
115                        //              private
116                        var tan58 = Math.tan(58 * Math.PI/180),
117                                cos32 = Math.cos(32 * Math.PI/180),
118                                sin32 = Math.sin(32 * Math.PI/180),
119                                tan32 = Math.tan(32 * Math.PI/180),
120                                w = this.w,
121                                h = this.h,
122                                page = this.page,
123                                turnfrom = this.turnfrom,
124                                params = this._styleParams;
125                       
126                        // Calculate each div size and position based on the page turning algorithm
127                        //       fw: frontWidth, fh: frontHeight, dw: dogear, cx: posX, cy: posY,
128                        //       dx: dogearX, dy: dogearY, fy:actualPagePos
129                        var Q = fold = w * tan58,
130                                fw = Q * sin32 + Q * cos32 * tan58,
131                                fh = fold + w + w/tan58,
132                                dw = w * 0.11 * this.dogear,
133                                pw = w - dw,
134                                base = pw * cos32,
135                                cx, cy, dx, dy, fy; // These params depend on the types of "turnfrom" parameter
136                       
137                        switch(this.turnfrom){ // TODO Should separate each case as a plugin?
138                        case "top":
139                                cx = fw - base;
140                                cy = base * tan58;
141                                dx = fw - dw;
142                                dy = cy + pw/tan58 - 7;
143                                fy = cy/cos32;
144                               
145                                params.init = {
146                                        page: css3.add({
147                                                top: -fy + "px",
148                                                left: (-fw + (page === 2 ? w : 0)) + "px",
149                                                width: fw + "px",
150                                                height: fh + "px"
151                                        }, {
152                                                transformOrigin: "100% 0%"
153                                        }),
154                                        front: css3.add({
155                                                width: w + "px",
156                                                height: h + "px"
157                                        }, {
158                                                boxShadow: "0 0"
159                                        }),
160                                        back: css3.add({
161                                                width: w + "px",
162                                                height: h + "px"
163                                        }, {
164                                                boxShadow: "0 0"
165                                        }),
166                                        shadow: {
167                                                display: "",
168                                                left: fw + "px",
169                                                height: h * 1.5 + "px"
170                                        }
171                                };
172                                params.turnForward = {
173                                        page: css3.add({}, {
174                                                transform: "rotate(0deg)"
175                                        }),
176                                        front: css3.add({}, {
177                                                transform: "translate("+ fw + "px," + fy +"px) rotate(0deg)",
178                                                transformOrigin: "-110px -18px"
179                                        }),
180                                        back: css3.add({}, {
181                                                transform: "translate("+ (fw - w) + "px," + fy +"px) rotate(0deg)",
182                                                transformOrigin: "0px 0px"
183                                        })
184                                };
185                                params.turnBackward = {
186                                        page: css3.add({}, {
187                                                transform: "rotate(-32deg)"
188                                        }),
189                                        front: css3.add({}, {
190                                                transform: "translate("+ cx + "px," + cy +"px) rotate(32deg)",
191                                                transformOrigin: "0px 0px"
192                                        }),
193                                        back: css3.add({}, {
194                                                transform: "translate("+ dx + "px," + dy +"px) rotate(-32deg)",
195                                                transformOrigin: "0px 0px"
196                                        })
197                                };
198                                break;
199                               
200                        case "bottom":
201                                cx = fw - (h * sin32 + w * cos32) - 2;
202                                cy = fh - (h + w/tan32) * cos32;
203                                dx = fw;
204                                dy = fh - w/sin32 - h;
205                                fy = fh - w/tan32 - h;
206                               
207                                params.init = {
208                                        page: css3.add({
209                                                top: (-fy + 50) + "px",
210                                                left: (-fw + (page === 2 ? w : 0)) + "px",
211                                                width: fw + "px",
212                                                height: fh + "px"
213                                        }, {
214                                                transformOrigin: "100% 100%"
215                                        }),
216                                        front: css3.add({
217                                                width: w + "px",
218                                                height: h + "px"
219                                        }, {
220                                                boxShadow: "0 0"
221                                        }),
222                                        back: css3.add({
223                                                width: w + "px",
224                                                height: h + "px"
225                                        }, {
226                                                boxShadow: "0 0"
227                                        }),
228                                        shadow: {
229                                                display: "none"
230                                        }
231                                };
232                                params.turnForward = {
233                                        page: css3.add({}, {
234                                                transform: "rotate(0deg)"
235                                        }),
236                                        front: css3.add({}, {
237                                                transform: "translate("+ fw + "px," + fy +"px) rotate(0deg)",
238                                                transformOrigin: "-220px 35px"
239                                        }),
240                                        back: css3.add({}, {
241                                                transform: "translate("+ (w * 2) + "px," + fy +"px) rotate(0deg)",
242                                                transformOrigin: "0px 0px"
243                                        })
244                                };
245                                params.turnBackward = {
246                                        page: css3.add({}, {
247                                                transform: "rotate(32deg)"
248                                        }),
249                                        front: css3.add({}, {
250                                                transform: "translate("+ cx + "px," + cy +"px) rotate(-32deg)",
251                                                transformOrigin: "0px 0px"
252                                        }),
253                                        back: css3.add({}, {
254                                                transform: "translate("+ dx + "px," + dy +"px) rotate(0deg)",
255                                                transformOrigin: "0px 0px"
256                                        })
257                                };
258                                break;
259                               
260                        case "left":
261                                cx = -w;
262                                cy = pw/tan32 - 2;
263                                dx = -pw;
264                                dy = fy = pw/sin32 + dw * sin32;
265                               
266                                params.init = {
267                                        page: css3.add({
268                                                top: -cy + "px",
269                                                left: w + "px",
270                                                width: fw + "px",
271                                                height: fh + "px"
272                                        }, {
273                                                transformOrigin: "0% 0%"
274                                        }),
275                                        front: css3.add({
276                                                width: w + "px",
277                                                height: h + "px"
278                                        }, {
279                                                boxShadow: "0 0"
280                                        }),
281                                        back: css3.add({
282                                                width: w + "px",
283                                                height: h + "px"
284                                        }, {
285                                                boxShadow: "0 0"
286                                        }),
287                                        shadow: {
288                                                display: "",
289                                                left: "-4px",
290                                                height: ((page === 2 ? h * 1.5 : h) + 50) + "px"
291                                        }
292                                };
293                                params.turnForward = {
294                                        page: css3.add({}, {
295                                                transform: "rotate(0deg)"
296                                        }),
297                                        front: css3.add({}, {
298                                                transform: "translate("+ cx + "px," + cy +"px) rotate(0deg)",
299                                                transformOrigin: "160px 68px"
300                                        }),
301                                        back: css3.add({}, {
302                                                transform: "translate(0px," + cy +"px) rotate(0deg)",
303                                                transformOrigin: "0px 0px"
304                                        })
305                                };
306                                params.turnBackward = {
307                                        page: css3.add({}, {
308                                                transform: "rotate(32deg)"
309                                        }),
310                                        front: css3.add({}, {
311                                                transform: "translate("+ (-dw) + "px," + dy +"px) rotate(-32deg)",
312                                                transformOrigin: "0px 0px"
313                                        }),
314                                        back: css3.add({}, {
315                                                transform: "translate("+ dx + "px," + dy +"px) rotate(32deg)",
316                                                transformOrigin: "top right"
317                                        })
318                                };
319                                break;
320                        }
321                       
322                        params.init.catalog = { // catalogNode
323                                width: (page === 2 ? w * 2 : w) + "px",
324                                height: ((page === 2 ? h * 1.5 : h) + (turnfrom == "top" ? 0 : 50)) + "px"
325                        };
326                };
327
328                this.getChildren = function(/*DomNode*/node){
329                        return array.filter(node.childNodes, function(n){ return n.nodeType === 1; });
330                };
331
332                this.getPages = function(){
333                        // summary:
334                        //              Get the array of all page nodes.
335                        return this._catalogNode ? this.getChildren(this._catalogNode) : null;
336                };
337
338                this.getCurrentPage = function(){
339                        // summary:
340                        //              Get the current page node.
341                        return this._currentPageNode;
342                };
343
344                this.getIndexOfPage = function(/*DomNode*/pageNode, /*DomNode[]?*/pages){
345                        if(!pages){
346                                pages = this.getPages();
347                        }
348                        for(var i=0; i<pages.length; i++){
349                                if(pageNode === pages[i]){ return i; }
350                        }
351                        return -1;
352                };
353
354                this.getNextPage = function(/*DomNode*/pageNode){
355                        for(var n = pageNode.nextSibling; n; n = n.nextSibling){
356                                if(n.nodeType === 1){ return n; }
357                        }
358                        return null;
359                };
360
361                this.getPreviousPage = function(/*DomNode*/pageNode){
362                        for(var n = pageNode.previousSibling; n; n = n.previousSibling){
363                                if(n.nodeType === 1){ return n; }
364                        }
365                        return null;
366                };
367
368                this.isPageTurned = function(/*DomNode*/pageNode){
369                        return pageNode.style[css3.name("transform")] == "rotate(0deg)";
370                };
371
372                this._onPageTurned = function(e){
373                        event.stop(e);
374                        if(domClass.contains(e.target, "mblPageTurningPage")){
375                                this.onPageTurned(e.target);
376                        }
377                };
378
379                this.onPageTurned = function(/*DomNode*/ /*===== pageNode =====*/){
380                        // summary:
381                        //              Stub function to which your application connects
382                        //              to handle the event when each page is tured
383                        // description:
384                        //              Called just after each page is turned.
385                };
386
387                this.initCatalog = function(/*DomNode*/catalogNode){
388                        // summary:
389                        //              Initializes the specified catalog/book.
390                        // description:
391                        //              Apply style class and other various styles to the specified catalog node
392                        //              for initialization.
393                        //              This function must be called every time when property values
394                        //              are changed by calling this.init(...) function.
395                        // catalogNode: DOMNode
396                        //              The catalog node to be initialized.
397                       
398                        if(this._catalogNode != catalogNode){
399                                if(this._transitionEndHandle){
400                                        connect.disconnect(this._transitionEndHandle);
401                                }
402                                this._transitionEndHandle = connect.connect(catalogNode, css3.name("transitionEnd"), this, "_onPageTurned")
403                                this._catalogNode = catalogNode;
404                        }
405                       
406                        // Initialize catalog node
407                        domClass.add(catalogNode, "mblPageTurningCatalog");
408                        domStyle.set(catalogNode, this._styleParams.init.catalog);
409                       
410                        // Initialize child pages
411                        var pages = this.getPages();
412                        array.forEach(pages, function(pageNode){
413                                this.initPage(pageNode);
414                        }, this);
415                       
416                        this.resetCatalog();
417                };
418
419                this._getBaseZIndex = function(){
420                        // Check z-index of catalogNode
421                        return this._catalogNode.style.zIndex || 0;
422                };
423
424                this.resetCatalog = function(){
425                        // summary:
426                        //              Resets the catalog by adjust the state of child pages.
427                        // description:
428                        //              Adjust z-index and dog-ear show/hide state of each child page.
429                        //              This function must be called when you add a new page
430                        //              or remove some page after initialization.
431                       
432                        // Adjust z-index and dog-ear of child pages
433                        var pages = this.getPages(),
434                                len = pages.length,
435                                base = this._getBaseZIndex();
436                        for(var i = len - 1; i>=0; i--){
437                                var pageNode = pages[i];
438                                this.showDogear(pageNode);
439                                if(this.isPageTurned(pageNode)){
440                                        pageNode.style.zIndex = base + len + 1;
441                                }else{
442                                        pageNode.style.zIndex = base + len - i;
443                                        !this.alwaysDogeared && this.hideDogear(pageNode);
444                                        this._currentPageNode = pageNode;
445                                }
446                        }
447                        if(!this.alwaysDogeared && this._currentPageNode != pages[len - 1]){
448                                this.showDogear(this._currentPageNode);
449                        }
450                };
451
452                this.initPage = function(/*DomNode*/pageNode, /*int?*/dir){
453                        // summary:
454                        //              Initializes the specified page.
455                        // description:
456                        //              Apply style class and other various styles to the specified page node
457                        //              for initialization.
458                        //              This function must be called when you add a new page after initialization.
459                        // pageNode: DOMNode
460                        //              The page node to be initialized.
461                        // dir: int?
462                        //              Specified whether the page is turned or not at initialization.
463                        //              This parameter should be either of 1 or -1.
464                        //              If 1, the page is turned at initialization. If -1, it is not turned.
465                       
466                        // Create shadow node if not exist
467                        var childNodes = this.getChildren(pageNode);
468                        while(childNodes.length < 3){
469                                pageNode.appendChild(domConstruct.create("div", null));
470                                childNodes = this.getChildren(pageNode);
471                        }
472                       
473                        // Check if it is the first time to initialize this page node or not
474                        var isFirst = !domClass.contains(pageNode, "mblPageTurningPage");
475                       
476                        // Apply style class
477                        domClass.add(pageNode, "mblPageTurningPage");
478                        domClass.add(childNodes[0], "mblPageTurningFront"); // frontNode
479                        domClass.add(childNodes[1], "mblPageTurningBack"); // backNode
480                        domClass.add(childNodes[2], "mblPageTurningShadow"); // shadowNode
481                       
482                        // Apply styles
483                        var p = this._styleParams.init;
484                        domStyle.set(pageNode, p.page);
485                        domStyle.set(childNodes[0], p.front); // frontNode
486                        domStyle.set(childNodes[1], p.back); // backNode
487                        p.shadow && domStyle.set(childNodes[2], p.shadow); // shadowNode
488                       
489                        if(!dir){
490                                // Determine whether to turn this page or not
491                                if(isFirst && this._currentPageNode){
492                                        var pages = this.getPages();
493                                        dir = this.getIndexOfPage(pageNode) < this.getIndexOfPage(this._currentPageNode) ? 1 : -1;
494                                }else{
495                                        dir = this.isPageTurned(pageNode) ? 1 : -1;
496                                }
497                        }
498                        this._turnPage(pageNode, dir, 0);
499                };
500
501                this.turnToNext = function(/*Number?*/duration){
502                        // summary:
503                        //              Turns a page forward.
504                        // description:
505                        //              The current page is turned forward if it is not the last page of the book.
506                        // duration: Number?
507                        //              The duration of page turning animations by seconds. (ex. 1.5, 3, etc)
508                        //              If this parameter is omitted, this.duration property value is used.
509                        var nextPage = this.getNextPage(this._currentPageNode);
510                        if(nextPage){
511                                this._turnPage(this._currentPageNode, 1, duration);
512                                this._currentPageNode = nextPage;
513                        }
514                };
515
516                this.turnToPrev = function(/*Number?*/duration){
517                        // summary:
518                        //              Turns a page backward.
519                        // description:
520                        //              The current page is turned backward if it is not the first page of the book.
521                        // duration: Number?
522                        //              The duration of page turning animations by seconds. (ex. 1.5, 3, etc)
523                        //              If this parameter is omitted, this.duration property value is used.
524                        var prevPage = this.getPreviousPage(this._currentPageNode);
525                        if(prevPage){
526                                this._turnPage(prevPage, -1, duration);
527                                this._currentPageNode = prevPage;
528                        }
529                };
530
531                this.goTo = function(/*int*/index){
532                        // summary:
533                        //              Jumps to the specified page without page turning animations.
534                        // index: int
535                        //              The index of the page you want to jump to.
536                        var pages = this.getPages();
537                        if(this._currentPageNode === pages[index] || pages.length <= index) { return; }
538                       
539                        var goBackward = index < this.getIndexOfPage(this._currentPageNode, pages);
540                        while(this._currentPageNode !== pages[index]) {
541                                goBackward ? this.turnToPrev(0) : this.turnToNext(0);
542                        }
543                };
544
545                this._turnPage = function(/*DomNode*/pageNode, /*int*/dir, /*Number?*/duration){
546                        // tags:
547                        //              private
548                        var childNodes = this.getChildren(pageNode),
549                                d = ((typeof duration !== 'undefined') ? duration : this.duration) + "s",
550                                p = (dir === 1) ? this._styleParams.turnForward : this._styleParams.turnBackward;
551                       
552                        // Apply styles for page turning animations
553                        p.page[css3.name("transitionDuration")] = d;
554                        domStyle.set(pageNode, p.page);
555                       
556                        p.front[css3.name("transitionDuration")] = d;
557                        domStyle.set(childNodes[0], p.front); // frontNode
558                       
559                        p.back[css3.name("transitionDuration")] = d;
560                        domStyle.set(childNodes[1], p.back); // backNode
561                       
562                        // Adjust z-index and dog-ear
563                        var pages = this.getPages(),
564                                nextPage = this.getNextPage(pageNode),
565                                len = pages.length,
566                                base = this._getBaseZIndex();
567                        if(dir === 1){
568                                pageNode.style.zIndex = base + len + 1;
569                                if(!this.alwaysDogeared && nextPage && this.getNextPage(nextPage)){
570                                        this.showDogear(nextPage);
571                                }
572                        }else{
573                                if(nextPage){
574                                        nextPage.style.zIndex = base + len - this.getIndexOfPage(nextPage, pages);
575                                        !this.alwaysDogeared && this.hideDogear(nextPage);
576                                }
577                        }
578                };
579
580                this.showDogear = function(/*DomNode*/pageNode){
581                        // summary:
582                        //              Shows the dog-ear.
583                        var childNodes = this.getChildren(pageNode);;
584                        domStyle.set(pageNode, "overflow", "");
585                        childNodes[1] && domStyle.set(childNodes[1], "display", ""); // backNode
586                        childNodes[2] && domStyle.set(childNodes[2], "display", this.turnfrom === "bottom" ? "none" : ""); // shadowNode
587                };
588
589                this.hideDogear = function(/*DomNode*/pageNode){
590                        // summary:
591                        //              Hides the dog-ear.
592                        if(this.turnfrom === "bottom"){ return; }
593                       
594                        var childNodes = this.getChildren(pageNode);
595                        domStyle.set(pageNode, "overflow", "visible");
596                        childNodes[1] && domStyle.set(childNodes[1], "display", "none"); // backNode
597                        childNodes[2] && domStyle.set(childNodes[2], "display", "none"); // shadowNode
598                };
599        };
600});
Note: See TracBrowser for help on using the repository browser.