2 * © 2007-2008 Benoît Pin – Centre de recherche en informatique – École des mines de Paris
4 * Licence Creative Commons http://creativecommons.org/licenses/by-nc/2.0/
5 * $Id: mosaique.js 1265 2009-08-05 08:22:59Z pin $
6 * $URL: http://svn.luxia.fr/svn/labo/projects/zope/Portfolio/trunk/skins/mosaique.js $
13 var hiddenTilesNumber
= 1;
17 Mosaique = function(screenArea
, imgUrlBase
, margins
) {
18 this.screenArea
= screenArea
;
20 margins
= {'top':0, 'right':0, 'bottom':0, 'left':0};
21 this.margins
= margins
;
23 this.setContainerPosition(new Point(0,0));
25 this.imgUrlBase
= imgUrlBase
;
26 this.xmlPath
= imgUrlBase
+ "/tiling_infos.xml";
29 this.xTileRange
= [0,0];
30 this.yTileRange
= [0,0];
32 this._loadingQueue
= new Array();
33 this._currentSequence
= null;
34 this._loadingIterator
= 0;
35 this.loadingState
= 0;
37 this.dragInProgress
= false;
38 this.initialClickPoint
= null;
39 this.initialPosition
= null;
41 this._ddHandlers
= {'down' : function(evt
){thisMos
._mouseDownHandler(evt
);},
42 'move' : function(evt
){thisMos
._mouseMoveHandler(evt
);},
43 'up' : function(evt
){thisMos
._mouseUpHandler(evt
);}};
44 this.ddHandlers
= null;
46 this.rcsItoC
= new Point(0, 0); // vector to translate coordinate systems
48 this.remainD
= new Point(0, 0);
52 Mosaique
.prototype.getXmlInfo = function() {
53 var req
= new XMLHttpRequest();
54 var thisMosaique
= this;
55 req
.onreadystatechange = function() {
56 if(req
.readyState
== 4)
57 thisMosaique
._loadXmlInfo(req
);
59 req
.open("GET",thisMosaique
.xmlPath
,true);
60 req
.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
64 Mosaique
.prototype._loadXmlInfo = function(req
) {
65 var doc
= req
.responseXML
.documentElement
;
66 this.thumbnailWidth
= parseInt(doc
.getElementsByTagName('thumbnailwidth')[0].firstChild
.data
);
67 this.thumbnailHeight
= parseInt(doc
.getElementsByTagName('thumbnailheight')[0].firstChild
.data
);
68 this.tileSize
= parseInt(doc
.getElementsByTagName('tilesize')[0].firstChild
.data
);
70 var zoomList
= doc
.getElementsByTagName("zoom");
71 this.zoomTable
= new Array(zoomList
.length
);
73 for (var i
=0 ; i
<zoomList
.length
; i
++) {
75 var zoomInfo
= new Object();
76 zoomInfo
['level'] = parseInt(zoom
.getAttribute('zoomlevel'));
77 zoomInfo
['width'] = parseInt(zoom
.getElementsByTagName('width')[0].firstChild
.data
);
78 zoomInfo
['height'] = parseInt(zoom
.getElementsByTagName('height')[0].firstChild
.data
);
79 zoomInfo
['tilesX'] = parseInt(zoom
.getElementsByTagName('tilesx')[0].firstChild
.data
);
80 zoomInfo
['tilesY'] = parseInt(zoom
.getElementsByTagName('tilesy')[0].firstChild
.data
);
81 this.zoomTable
[i
] = zoomInfo
;
84 this.setCurrentDimensionValues(this.getBestFitZoom());
85 this.navigateur
= new Navigateur(this);
87 this.prepareContainer();
88 this.setLoadingOrder();
89 var ulc
= new Point(this.imageWidth
/2 - this.screenWidth/2, 0);
90 if (this.screenHeight
>= this.imageHeight
)
91 ulc
.y
= this.imageHeight
/2 - this.screenHeight/2;
93 this.addEventListeners();
96 Mosaique
.prototype.addEventListeners = function() {
98 addListener(document
, 'mousedown', function(evt
){thisMos
.mouseDownHandler(evt
);}, 'mosaique.dd');
99 addListener(document
, 'mouseup', function(evt
){thisMos
.mouseUpHandler(evt
);}, 'mosaique.dd');
102 Mosaique
.prototype.getBestFitZoom = function() {
104 for (i
= 1 ; i
< this.zoomTable
.length
; i
++)
105 if (this.screenWidth
- this.zoomTable
[i
]['width'] < 0 ||
106 this.screenHeight
- this.zoomTable
[i
]['height'] < 0)
111 Mosaique
.prototype.setCurrentDimensionValues = function(zoomIndex
) {
112 var zoomInfo
= this.zoomTable
[zoomIndex
];
113 this.zoomIndex
= zoomIndex
;
114 this.zoomLevel
= zoomInfo
['level'];
115 this.imageWidth
= zoomInfo
['width'];
116 this.imageHeight
= zoomInfo
['height'];
117 this.xtiles
= zoomInfo
['tilesX'];
118 this.ytiles
= zoomInfo
['tilesY'];
119 this.halfTileSize
= this.tileSize
/ 2;
120 this.gridWidth
= this.xtiles
*this.tileSize
;
121 this.gridHeight
= this.ytiles
*this.tileSize
;
123 var WnbTiles
= Math
.ceil(this.screenWidth
/this.tileSize
) + 2 * hiddenTilesNumber
;
124 var HnbTiles
= Math
.ceil(this.screenHeight
/this.tileSize
) + 2 * hiddenTilesNumber
;
125 this.WnbTiles
= (WnbTiles
> this.xtiles
) ? this.xtiles
: WnbTiles
;
126 this.HnbTiles
= (HnbTiles
> this.ytiles
) ? this.ytiles
: HnbTiles
;
130 Mosaique
.prototype.prepareScreen = function() {
131 this.screenWidth
= getObjectWidth(this.screenArea
);
132 this.screenHeight
= getObjectHeight(this.screenArea
);
133 var mask
= this.rootElement
= document
.createElement('div');
135 position
= 'absolute';
136 width
= this.screenWidth
- this.margins
['right'] + 'px';
137 height
= this.screenHeight
- this.margins
['bottom'] + 'px';
138 background
= base_properties
["backgroundColor"];
141 this.screenArea
.insertBefore(mask
, this.screenArea
.firstChild
);
143 var container
= document
.createElement('div');
144 with (container
.style
) {
145 position
= 'absolute';
151 mask
.appendChild(container
);
152 this.container
= container
;
155 Mosaique
.prototype.prepareContainer = function() {
157 if (browser
.isGecko
) {
158 __getImg = function(evt
, o
) {
163 __getImg = function(evt
) {
164 return getTargetedObject(evt
);
170 loadNext = function(evt
) {
171 var tile
= __getImg(evt
, this);
172 tile
.style
.visibility
= 'visible';
173 setTimeout(function(){thisMos
._loadNextTile();}, 1);
177 loadNext = function(evt
) {
178 var tile
= __getImg(evt
, this);
179 tile
.style
.visibility
= 'visible';
180 thisMos
._loadNextTile();
184 this.setContainerPosition(new Point(0,0));
185 this.xTileRange
= [0,0];
186 this.yTileRange
= [0,0];
188 var size
= parseInt(this.tileSize
);
189 this.tiles
= new Array(this.WnbTiles
);
190 for (var x
= 0 ; x
< this.WnbTiles
; x
++) {
191 this.tiles
[x
] = new Array(this.HnbTiles
);
192 for (var y
= 0 ; y
< this.HnbTiles
; y
++) {
193 var img
= document
.createElement("img");
198 addListener(img
, 'load', loadNext
, 'mosaique.tiles');
199 img
.style
.position
='absolute';
200 img
.style
.left
= x
* size
+ 'px';
201 img
.style
.top
= y
* size
+ 'px';
202 this.container
.appendChild(img
);
203 this.tiles
[x
][y
] = img
;
206 with(this.container
.style
) {
207 width
= size
* this.WnbTiles
+ 'px';
208 height
= size
* this.HnbTiles
+ 'px';
213 Mosaique
.prototype.setContainerPosition = function(point
) {
214 with(this.container
.style
) {
215 left
= point
.x
+ 'px';
216 top
= point
.y
+ 'px';
220 Mosaique
.prototype.getContainerPosition = function() {
221 var x
= parseInt(this.container
.style
.left
);
222 var y
= parseInt(this.container
.style
.top
);
223 var p
= new Point(x
, y
);
227 Mosaique
.prototype.setImagePosition = function(point
) {
228 this.setContainerPosition(this.rcsItoC
.diff(point
));
231 Mosaique
.prototype.getImagePosition = function() {
232 var cp
= this.getContainerPosition();
233 return this.rcsItoC
.diff(cp
);
236 Mosaique
.prototype.getImageCenterPosition = function() {
237 var ip
= this.getImagePosition();
238 return ip
.add(new Point(this.screenWidth
/2, this.screenHeight/2));
243 Mosaique
.prototype.loadScreen = function(position
) {
245 var tSize
= this.tileSize
;
246 var ulTileCoord
= new Point(Math
.floor(position
.x
/tSize), Math.floor(position.y/tSize
));
247 var modulo
= new Point(position
.x
% tSize
, position
.y
% tSize
);
250 if (modulo
.x
> this.halfTileSize
) {
251 adjX = function(n
){return n
- hiddenTilesNumber
+ 1;};
252 this.remainD
.x
= - (modulo
.x
- tSize
);
255 adjX = function(n
){return n
- hiddenTilesNumber
;};
256 this.remainD
.x
= - modulo
.x
;
258 if (modulo
.y
> this.halfTileSize
) {
259 this.remainD
.y
= - (modulo
.y
- tSize
);
260 adjY = function(n
){return n
- hiddenTilesNumber
+ 1;};
263 adjY = function(n
){return n
- hiddenTilesNumber
;};
264 this.remainD
.y
= - modulo
.y
;
267 var xTileRange
, yTileRange
;
268 xTileRange
= [ulTileCoord
.x
, ulTileCoord
.x
+ this.WnbTiles
].map(adjX
);
269 yTileRange
= [ulTileCoord
.y
, ulTileCoord
.y
+ this.HnbTiles
].map(adjY
);
271 //console.assert(xTileRange[1] - xTileRange[0] == this.WnbTiles, xTileRange, this.WnbTiles);
272 //console.assert(yTileRange[1] - yTileRange[0] == this.HnbTiles, yTileRange, this.HnbTiles);
274 if (xTileRange
[0] < 0) {
275 this.remainD
.x
= Math
.abs(xTileRange
[0] + ((position
.x
< 0) ? 1 : 0)) * tSize
- modulo
.x
;
276 xTileRange
= [0, this.WnbTiles
];
278 else if (xTileRange
[1] > this.xtiles
) {
279 this.remainD
.x
= - (xTileRange
[1] - this.xtiles
- ((modulo
.x
> this.halfTileSize
) ? 1 : 0)) * tSize
- modulo
.x
;
280 xTileRange
= [this.xtiles
- this.WnbTiles
, this.xtiles
];
283 if (yTileRange
[0] < 0) {
284 this.remainD
.y
= Math
.abs(yTileRange
[0] + ((position
.y
< 0) ? 1 : 0)) * tSize
- modulo
.y
;
285 yTileRange
= [0, this.HnbTiles
];
287 else if (yTileRange
[1] > this.ytiles
) {
288 this.remainD
.y
= - (yTileRange
[1] - this.ytiles
- ((modulo
.y
> this.halfTileSize
) ? 1 : 0)) * tSize
- modulo
.y
;
289 yTileRange
= [this.ytiles
- this.HnbTiles
, this.ytiles
];
292 //console.assert(xTileRange[0] >= 0 && xTileRange[1] <= this.xtiles);
293 //console.assert(yTileRange[0] >= 0 && yTileRange[1] <= this.ytiles);
295 var dTx
= this.xTileRange
[0] - xTileRange
[0];
296 var dTy
= this.yTileRange
[0] - yTileRange
[0];
297 this.rcsItoC
= this.rcsItoC
.diff(new Point(dTx
* tSize
, dTy
* tSize
));
299 this.setImagePosition(position
);
301 this.xTileRange
= xTileRange
;
302 this.yTileRange
= yTileRange
;
304 var xOffset
= this.xTileRange
[0];
305 var yOffset
= this.yTileRange
[0];
307 var baseUrl
= this.imgUrlBase
+ '/getTile?zoom=' + this.zoomLevel
/ 100.0;
309 var tilesSrc
= new Array(this.WnbTiles
);
310 for (var x
= 0 ; x
< this.WnbTiles
; x
++) {
311 tilesSrc
[x
] = new Array(this.HnbTiles
);
312 for (var y
= 0 ; y
< this.HnbTiles
; y
++) {
313 tilesSrc
[x
][y
] = baseUrl
+ '&x=' + (x
+ xOffset
) + '&y=' + (y
+ yOffset
);
316 this.queueLoadingSequence({'type':'full',
318 'order' : this.loadingOrder
,
319 'length' : this.loadingOrder
.length
});
322 Mosaique
.prototype.loadColumns = function(n
){
324 n > 0 <=> x displacement > 0 => shift columns from right to left
325 Returns the number of columns that have been loaded.
332 newRange
= [this.xTileRange
[0] - n
, this.xTileRange
[1] - n
]
333 if (newRange
[0]<0 || newRange
[1] > this.xtiles
) {
338 this.xTileRange
= newRange
;
345 var shift
, from, to
, increment
;
347 shift
= - (this.WnbTiles
) * this.tileSize
;
353 shift
= (this.WnbTiles
) * this.tileSize
;
354 from = this.WnbTiles
- 1;
355 to
= this.WnbTiles
+ n
-1;
360 var beforeSequence = function(){thisMos
._shiftColumns(n
, shift
, from, to
, increment
)};
362 var order
= new Array();
363 var tilesSrc
= new Array();
364 var baseUrl
= this.imgUrlBase
+ '/getTile?zoom=' + this.zoomLevel
/ 100.0;
365 var xOffset
= this.xTileRange
[0];
366 var yOffset
= this.yTileRange
[0];
368 for (var x
= from ; x
!= to
; x
+= increment
) {
369 tilesSrc
[x
] = new Array();
370 for (var y
= 0 ; y
< this.HnbTiles
; y
++) {
372 tilesSrc
[x
][y
] = baseUrl
+ '&x=' + (x
+ xOffset
) + '&y=' + (y
+ yOffset
);
375 order
= order
.reverse();
376 this.queueLoadingSequence({'type':'column',
379 'length' : order
.length
,
380 'beforeSequence' : beforeSequence
});
384 Mosaique
.prototype._shiftColumns = function(n
, shift
, from, to
, increment
){
386 this.tiles
= rotateArray(this.tiles
, -n
);
388 /* positional shifting */
389 for (var x
= from ; x
!= to
; x
+= increment
) {
390 left
= parseInt(this.tiles
[x
][0].style
.left
);
391 for (var y
= 0 ; y
< this.HnbTiles
; y
++) {
392 var tile
= this.tiles
[x
][y
];
393 tile
.style
.left
= left
+ shift
+ 'px';
394 tile
.style
.visibility
= 'hidden';
402 Mosaique
.prototype.loadRows = function(n
) {
404 n > 0 <=> y displacement > 0 => shift rows from bottom to top
405 Returns the number of rows that's have been loaded.
411 newRange
= [this.yTileRange
[0] - n
, this.yTileRange
[1] - n
]
412 if (newRange
[0]<0 || newRange
[1] > this.ytiles
) {
417 this.yTileRange
= newRange
;
423 var shift
, from, to
, increment
;
425 shift
= - (this.HnbTiles
) * this.tileSize
;
431 shift
= (this.HnbTiles
) * this.tileSize
;
432 from = this.HnbTiles
- 1;
433 to
= this.HnbTiles
+ n
-1;
438 var beforeSequence = function(){thisMos
._shiftRows(n
, shift
, from, to
, increment
)};
440 var order
= new Array();
441 var tilesSrc
= new Array();
442 var baseUrl
= this.imgUrlBase
+ '/getTile?zoom=' + this.zoomLevel
/ 100.0;
443 var xOffset
= this.xTileRange
[0];
444 var yOffset
= this.yTileRange
[0];
446 for (var y
= from ; y
!= to
; y
+= increment
) {
447 for (var x
= 0 ; x
< this.WnbTiles
; x
++) {
450 tilesSrc
[x
] = new Array();
451 tilesSrc
[x
][y
] = baseUrl
+ '&x=' + (x
+ xOffset
) + '&y=' + (y
+ yOffset
);
454 order
= order
.reverse();
455 this.queueLoadingSequence({'type':'row',
458 'length' : order
.length
,
459 'beforeSequence' : beforeSequence
});
463 Mosaique
.prototype._shiftRows = function(n
, shift
, from, to
, increment
) {
464 /* columns rotations */
465 for (var x
= 0 ; x
< this.WnbTiles
; x
++)
466 this.tiles
[x
] = rotateArray(this.tiles
[x
], -n
);
470 /* positional shifting */
471 for (var y
= from ; y
!= to
; y
+= increment
) {
472 top
= parseInt(this.tiles
[0][y
].style
.top
);
473 for (var x
= 0 ; x
< this.WnbTiles
; x
++) {
474 var tile
= this.tiles
[x
][y
];
475 tile
.style
.top
= top
+ shift
+ 'px';
476 tile
.style
.visibility
= 'hidden';
482 Mosaique
.prototype.queueLoadingSequence = function(sequenceInfo
) {
483 if(!sequenceInfo
.length
) return;
484 this._loadingQueue
.push(sequenceInfo
);
485 if (!this.loadingState
&& this._loadingQueue
.length
)
486 this._loadNextSequence();
489 Mosaique
.prototype._loadNextSequence = function() {
490 var seq
= this._loadingQueue
.shift();
492 this._loadingQueue
= new Array();
493 this.loadingState
= 0;
496 switch(seq
['type']) {
498 this.loadingState
= 1;
502 this.loadingState
= 2;
503 seq
['beforeSequence']();
506 this._loadingIterator
= 0;
507 //this._loadNextTile();
508 this._startSequence(seq
);
511 Mosaique
.prototype._startSequence = function(seq
) {
512 this._currentSequence
= seq
;
514 var size
= Math
.min(batchSize
, this._currentSequence
.length
);
515 this._loadingIterator
+= size
;
517 var coord
, src
, tile
;
518 for (var i
=0 ; i
<size
; i
++) {
519 coord
= this._currentSequence
.order
[i
];
520 src
= this._currentSequence
.src
[coord
[0]][coord
[1]];
521 tile
= this.tiles
[coord
[0]][coord
[1]];
526 Mosaique
.prototype._loadNextTile = function() {
527 if (this.loadingState
== 0)
529 else if (this._loadingIterator
>= this._currentSequence
['length']) {
530 this._loadNextSequence();
534 var coord
= this._currentSequence
.order
[this._loadingIterator
];
535 this._loadingIterator
++;
537 var src
= this._currentSequence
.src
[coord
[0]][coord
[1]];
538 var tile
= this.tiles
[coord
[0]][coord
[1]];
543 /* drag and drop generic handlers */
544 Mosaique
.prototype.mouseDownHandler = function(evt
) {
545 var target
= getTargetedObject(evt
);
546 if (target
.tagName
== 'INPUT' || target
.tagName
== 'TEXTAREA')
549 evt
= getEventObject(evt
);
550 var navDisp
= this.navigateur
.display
;
552 if (target
.parentNode
.parentNode
== navDisp
) {
553 if (target
== this.navigateur
.frame
.firstChild
)
554 this.ddHandlers
= this.navigateur
._ddHandlers
;
556 this.ddHandlers
= null;
561 this.ddHandlers
= this._ddHandlers
;
563 addListener(document
, 'mousemove', this.ddHandlers
['move'], 'mosaique.dd');
565 this.ddHandlers
['down'](evt
);
568 Mosaique
.prototype.mouseUpHandler = function(evt
) {
569 if (this.ddHandlers
!= null) {
570 removeListener(document
, 'mousemove', this.ddHandlers
['move']);
571 this.ddHandlers
['up'](evt
);
572 this.ddHandlers
= null;
577 /* Mosaique drag and drop handlers */
578 Mosaique
.prototype._mouseDownHandler = function(evt
) {
579 this.initialClickPoint
= new Point(evt
.clientX
, evt
.clientY
);
580 this.initialPosition
= this.getContainerPosition();
583 this.dragInProgress
= true;
586 Mosaique
.prototype._mouseMoveHandler = function(evt
) {
588 if(!this.dragInProgress
)
591 evt
= getEventObject(evt
);
592 var currentPoint
= new Point(evt
.clientX
, evt
.clientY
);
593 var displacement
= currentPoint
.diff(this.initialClickPoint
);
594 this.setContainerPosition(this.initialPosition
.add(displacement
));
596 var r
= (displacement
.y
+ this.halfTileSize
+ this.remainD
.y
) / this.tileSize
;
599 if (this.rShift
- r
!= 0)
600 this.rShift
+= this.loadRows(r
- this.rShift
);
602 var c
= (displacement
.x
+ this.halfTileSize
+ this.remainD
.x
) / this.tileSize
;
605 if (this.cShift
- c
!= 0)
606 this.cShift
+= this.loadColumns(c
- this.cShift
);
609 Mosaique
.prototype._mouseUpHandler = function(evt
) {
610 this.dragInProgress
= false;
611 evt
= getEventObject(evt
);
612 this._mouseMoveHandler(evt
);
613 var finalPoint
= new Point(evt
.clientX
, evt
.clientY
);
614 var displacement
= finalPoint
.diff(this.initialClickPoint
);
615 this.remainD
= this.remainD
.add(new Point(displacement
.x
- this.cShift
* this.tileSize
, displacement
.y
- this.rShift
* this.tileSize
));
616 this.navigateur
.alignFrame();
621 Mosaique
.prototype.setLoadingOrder = function() {
623 var stopX
= this.WnbTiles
;
625 var stopY
= this.HnbTiles
;
627 var order
= new Array();
632 while((startX
!= stopX
) && (startY
!= stopY
)) {
634 case 0 : // left -> right
637 for (x
= startX
; x
< stopX
; x
++)
642 case 1 : // up -> bottom
644 for (y
= startY
; y
< stopY
; y
++)
649 case 2 : // right -> left
652 for (x
= stopX
-1 ; x
>= startX
; x
--)
657 case 3 : // bottom -> up
660 for (y
= stopY
-1 ; y
>= startY
; y
--)
667 if (direction
% 4 == 0)
672 this.loadingOrder
= order
.reverse();
675 Mosaique
.prototype.cleanContainer = function() {
676 removeGroupListeners('mosaique.tiles');
677 while (this.container
.childNodes
[0])
678 this.container
.removeChild(this.container
.childNodes
[0]);
681 Mosaique
.prototype.loadZoomLevel = function(zoomIndex
) {
682 this.loadingState
= 0;
683 var oldWnbTiles
= this.WnbTiles
;
684 var oldHnbTiles
= this.HnbTiles
;
685 var oldCenter
= this.getImageCenterPosition();
686 var zoomInfo
= this.zoomTable
[zoomIndex
];
687 var newLevel
= zoomInfo
['level'];
689 // center coordinates translated into target zoom level
690 var center
= oldCenter
.mul(newLevel
/ this.zoomLevel
);
691 var ulc
= center
.diff(new Point(this.screenWidth
/2, this.screenHeight/2)); // upper left corner
693 this.setCurrentDimensionValues(zoomIndex
);
695 if (oldWnbTiles
!= this.WnbTiles
|| oldHnbTiles
!= this.HnbTiles
) {
696 this.cleanContainer();
697 this.prepareContainer();
698 this.rcsItoC
= new Point(0,0);
699 this.setLoadingOrder();
701 this.loadScreen(ulc
);
704 Mosaique
.prototype.unload = function() {
705 this.navigateur
.unload();
706 removeGroupListeners('mosaique.dd');
707 removeGroupListeners('mosaique.tiles');
708 this.screenArea
.removeChild(this.rootElement
);
714 function Point(x
, y
) {
715 this.x
= Math
.round(x
);
716 this.y
= Math
.round(y
);
718 Point
.prototype.diff = function(point
) { return new Point(this.x
- point
.x
, this.y
- point
.y
); };
719 Point
.prototype.add = function(point
) { return new Point(this.x
+ point
.x
, this.y
+ point
.y
); };
720 Point
.prototype.mul = function(k
) { return new Point(this.x
* k
, this.y
*k
)};
721 Point
.prototype.toString = function() { return "(" + String(this.x
) + ", " + String(this.y
) + ")"; };
723 function rotateArray(t
, n
) {
724 return t
.slice(n
,t
.length
).concat(t
.slice(0,n
));
727 if (!Array
.prototype.map
) {
728 Array
.prototype.map = function(f
) {
729 var r
= new Array(this.length
);
730 for (var i
= 0 ; i
< this.length
; i
++ ){