├── .gitignore
├── README.md
├── demos
├── demo.css
├── demo.js
├── dev-tiles-sprite.png
└── index.html
├── dist
├── tiles.js
└── tiles.min.js
├── gruntfile.js
├── package.json
└── src
├── Grid.js
├── Template.js
├── Tile.js
└── UniformTemplates.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore node modules
2 | node_modules
3 |
4 | # Webstorm
5 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tiles.js
2 | =====
3 |
4 | ###Goal###
5 | Make it easy to create grid based layouts which can adapt to various screen sizes and changing content.
6 |
7 | ###How###
8 | The Tiles.js library provides a grid control and a simple template language for defining layouts. It uses jQuery to animate tiles when either the content or resolution changes.
9 |
10 | ###Demo###
11 | See the tiles in action on [Pulse for the Web](http://www.pulse.me/)!
12 |
13 | Once you sign-in to Pulse, check out the [Behind the Scenes](http://www.pulse.me/app/dev) page which includes more info about the tiles and a live editor to experiment with templates.
14 |
15 | ###Install###
16 |
17 | npm install tilesjs
18 |
19 |
20 | Or you can download binaries from the dist folder
21 |
22 | Compiled size: 6 KB (just over 2 KB gzipped).
23 |
24 | ###Sample Code###
25 | There are 2 samples in the demos directory. A proper site with documentation and additional samples is coming soon...
26 |
27 | ###Tile###
28 | A tile is a rectangular element that covers one or more cells in a grid. Each tile has a unique identifier and maintains its current position in the grid (top, left, width, height).
29 |
30 | The tile handles several events during its lifecycle:
31 |
32 | * appendTo: tile should be appended to the parent grid element.
33 | * remove: tile should be removed from the parent grid
34 | * resize: tile is resized (or moved) within the parent grid
35 |
36 | ###Template###
37 | A template specifies the layout of variably sized tiles in a grid. We provide a simple JSON based template language for defining templates. A single cell tile should use the period character. Larger tiles may be created using any character that is unused by a adjacent tile. Whitespace is ignored when parsing the rows.
38 |
39 | Examples:
40 |
41 | var simpleTemplate = [
42 | ' A A . B ',
43 | ' A A . B ',
44 | ' . C C . ',
45 | ];
46 |
47 | var complexTemplate = [
48 | ' J J . . E E ',
49 | ' . A A . E E ',
50 | ' B A A F F . ',
51 | ' B . D D . H ',
52 | ' C C D D G H ',
53 | ' C C . . G . ',
54 | ];
55 |
56 | In addition to creating templates using JSON, you can also programmatically build templates. The library includes a simple UniformTemplate factory which creates 1x1 templates for a given number of columns and tiles. Custom template factories can be created to generate content aware layouts.
57 |
58 | ###Grid###
59 | The grid control renders a set of tiles into a template. The grid was designed to fill available screen area by either scaling the size of a cell or by requesting a new template with a different number of columns.
60 |
61 | It was also designed for a changing set of content. When tiles or template change, the grid will instruct the tiles to either fade in, animate, or fade out to their new location.
62 |
63 | Updates and Redraw are separate processes, so a series of updates may be made to the content followed by a single redraw to trigger the animation when appropriate. During the redraw phase, the grid has a prioritization extensibility point. Custom grid controls can be created to order the content prior to assigning each tile a spot in the grid.
64 |
65 |
66 | ## The MIT License ##
67 |
68 | Copyright (c) 2012 Pixel Lab
69 |
70 | Permission is hereby granted, free of charge, to any person obtaining a copy
71 | of this software and associated documentation files (the "Software"), to deal
72 | in the Software without restriction, including without limitation the rights
73 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
74 | copies of the Software, and to permit persons to whom the Software is
75 | furnished to do so, subject to the following conditions:
76 |
77 | The above copyright notice and this permission notice shall be included in
78 | all copies or substantial portions of the Software.
79 |
80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
81 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
82 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
83 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
84 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
85 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
86 | THE SOFTWARE.
--------------------------------------------------------------------------------
/demos/demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 20px;
3 | font-family: Arial;
4 | }
5 |
6 | .grid {
7 | width: 95%;
8 | height: 600px;
9 | position: relative;
10 | overflow-x: hidden;
11 | overflow-y: scroll;
12 | background-color: #ddd;
13 | margin: 10px 0px;
14 | border: 10px solid #ddd;
15 | clear: both;
16 | }
17 |
18 | .grid > div {
19 | background-color: #fff;
20 | position: absolute;
21 | }
22 |
23 | /* slider for sample 1 */
24 |
25 | .slider {
26 | width: 200px;
27 | display: inline-block;
28 | margin: 0px 10px;
29 | }
30 |
31 | .sliderLabel {
32 | font-size: 18px;
33 | font-weight: bold;
34 | }
35 |
36 | /* template selections for sample #2 */
37 |
38 | .dev-tile-number, .dev-tile-size {
39 | font-size: 36px;
40 | padding: 10px;
41 | }
42 |
43 | .dev-tiles-templates ul {
44 | margin-bottom: 10px;
45 | }
46 |
47 | .dev-template {
48 | margin-right: 20px;
49 | height: 35px;
50 | display: inline-block;
51 | background: url(dev-tiles-sprite.png) no-repeat;
52 | cursor: pointer;
53 | }
54 |
55 | .dev-template.selected {
56 | background-position-y: -200px;
57 | }
58 |
59 | .dev-l1 {
60 | width: 47px;
61 | }
62 |
63 | .dev-l2 {
64 | width: 47px;
65 | background-position-x: -68px;
66 | }
67 |
68 | .dev-l3 {
69 | width: 39px;
70 | background-position-x: -135px;
71 | }
72 |
73 | .dev-l4 {
74 | width: 39px;
75 | background-position-x: -194px;
76 | }
77 |
78 | .dev-l5 {
79 | width: 47px;
80 | background-position-x: -245px;
81 | margin-right: 0px;
82 | }
--------------------------------------------------------------------------------
/demos/demo.js:
--------------------------------------------------------------------------------
1 | // The grid manages tiles using ids, which you can define. For our
2 | // examples we'll just use the tile number as the unique id.
3 | var TILE_IDS = [
4 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
5 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
6 | ];
7 |
8 | // SAMPLE #1
9 | $(function() {
10 |
11 | // create the grid and an event which will update the grid
12 | // when either tile count or window size changes
13 | var el = document.getElementById('sample1-grid'),
14 | grid = new Tiles.Grid(el),
15 | updateGrid = function(event, ui) {
16 |
17 | // update the set of tiles and redraw the grid
18 | grid.updateTiles(TILE_IDS.slice(0, ui.value));
19 | grid.redraw(true /* animate tile movements */);
20 |
21 | // update the tile count label
22 | $('#tileCount').text('(' + ui.value + ')');
23 | };
24 |
25 | // use a jQuery slider to update the number of tiles
26 | $('#sample1-tiles')
27 | .slider({
28 | min: 1,
29 | max: 25,
30 | step: 1,
31 | create: updateGrid,
32 | slide: updateGrid,
33 | change: updateGrid
34 | })
35 | .slider('value', 8);
36 |
37 | // wait until user finishes resizing the browser
38 | var debouncedResize = debounce(function() {
39 | grid.resize();
40 | grid.redraw(true);
41 | }, 200);
42 |
43 | // when the window resizes, redraw the grid
44 | $(window).resize(debouncedResize);
45 | });
46 |
47 | // templates in JSON matching the predefined selections you can
48 | // choose on the demo page
49 | var DemoTemplateRows = [
50 | [
51 | " A A B B C C ",
52 | " A A B B C C ",
53 | " . . . . . . ",
54 | " D D E E F F "
55 | ], [
56 | " A A A A A A ",
57 | " B B C C D D ",
58 | " B B C C D D ",
59 | " B B C C D D "
60 | ], [
61 | " A A B B . ",
62 | " A A B B . ",
63 | " A A C C . ",
64 | " . . . . ."
65 | ], [
66 | " A A . . ",
67 | " A A . . ",
68 | " B B . . ",
69 | " C C . ."
70 | ], [
71 | " A A A B B B ",
72 | " A A A B B B ",
73 | " A A A C C . ",
74 | " . . . . . ."
75 | ]
76 | ];
77 |
78 | // SAMPLE #2
79 | $(function() {
80 |
81 | var el = document.getElementById('sample2-grid'),
82 | grid = new Tiles.Grid(el);
83 |
84 | // template is selected by user, not generated so just
85 | // return the number of columns in the current template
86 | grid.resizeColumns = function() {
87 | return this.template.numCols;
88 | };
89 |
90 | // by default, each tile is an empty div, we'll override creation
91 | // to add a tile number to each div
92 | grid.createTile = function(tileId) {
93 | var tile = new Tiles.Tile(tileId);
94 | tile.$el.append('
' + tileId + '
');
95 | return tile;
96 | };
97 |
98 | // update the template selection
99 | var $templateButtons = $('#sample2-templates .dev-template').on('click', function(e) {
100 |
101 | // unselect all templates
102 | $templateButtons.removeClass("selected");
103 |
104 | // select the template we clicked on
105 | $(e.target).addClass("selected");
106 |
107 | // get the JSON rows for the selection
108 | var index = $(e.target).index(),
109 | rows = DemoTemplateRows[index];
110 |
111 | // set the new template and resize the grid
112 | grid.template = Tiles.Template.fromJSON(rows);
113 | grid.isDirty = true;
114 | grid.resize();
115 |
116 | // adjust number of tiles to match selected template
117 | var ids = TILE_IDS.slice(0, grid.template.rects.length);
118 | grid.updateTiles(ids);
119 | grid.redraw(true);
120 | });
121 |
122 | // make the initial selection
123 | $('#sample2-t1').trigger('click');
124 |
125 | // wait until users finishes resizing the browser
126 | var debouncedResize = debounce(function() {
127 | grid.resize();
128 | grid.redraw(true);
129 | }, 200);
130 |
131 | // when the window resizes, redraw the grid
132 | $(window).resize(debouncedResize);
133 | });
134 |
135 | // SAMPLE #3
136 | $(function() {
137 |
138 | // create a custom Tile which customizes the resize behavior
139 | function CustomTile(tileId, element) {
140 | // initialize base
141 | Tiles.Tile.call(this, tileId, element);
142 | }
143 |
144 | CustomTile.prototype = new Tiles.Tile();
145 |
146 | CustomTile.prototype.resize = function(cellRect, pixelRect, animate, duration, onComplete) {
147 |
148 | // set the text inside the tile to the dimensions
149 | var cellDimensions = cellRect.width + ' x ' + cellRect.height;
150 | this.$el.find('.dev-tile-size').text(cellDimensions);
151 |
152 | // call the base to perform the resize
153 | Tiles.Tile.prototype.resize.call(
154 | this, cellRect, pixelRect, animate, duration, onComplete);
155 | };
156 |
157 |
158 | var el = document.getElementById('sample3-grid'),
159 | grid = new Tiles.Grid(el);
160 |
161 | // template is selected by user, not generated so just
162 | // return the number of columns in the current template
163 | grid.resizeColumns = function() {
164 | return this.template.numCols;
165 | };
166 |
167 | // we'll override creation to use our custom tile
168 | grid.createTile = function(tileId) {
169 | var tile = new CustomTile(tileId);
170 | tile.$el.append('');
171 | return tile;
172 | };
173 |
174 | // update the template selection
175 | var $templateButtons = $('#sample3-templates .dev-template').on('click', function(e) {
176 |
177 | // unselect all templates
178 | $templateButtons.removeClass("selected");
179 |
180 | // select the template we clicked on
181 | $(e.target).addClass("selected");
182 |
183 | // get the JSON rows for the selection
184 | var index = $(e.target).index(),
185 | rows = DemoTemplateRows[index];
186 |
187 | // set the new template and resize the grid
188 | grid.template = Tiles.Template.fromJSON(rows);
189 | grid.isDirty = true;
190 | grid.resize();
191 |
192 | // adjust number of tiles to match selected template
193 | var ids = TILE_IDS.slice(0, grid.template.rects.length);
194 | grid.updateTiles(ids);
195 | grid.redraw(true);
196 | });
197 |
198 | // make the initial selection
199 | $('#sample3-t1').trigger('click');
200 |
201 | // wait until users finishes resizing the browser
202 | var debouncedResize = debounce(function() {
203 | grid.resize();
204 | grid.redraw(true);
205 | }, 200);
206 |
207 | // when the window resizes, redraw the grid
208 | $(window).resize(debouncedResize);
209 | });
210 |
--------------------------------------------------------------------------------
/demos/dev-tiles-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thinkpixellab/tilesjs/5fb74fd35c9aba9ce952d839b7ba3e8cb88779d6/demos/dev-tiles-sprite.png
--------------------------------------------------------------------------------
/demos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Tiles.js Basic Demos
6 |
7 |
8 |
9 |
10 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
42 |
Sample 1: Resizable grid using 1x1 tiles
43 |
44 | Let's start with a a simple resizable grid of 1x1 tiles. We'll create a Tiles.js grid and
45 | provide a parent div which will hold our tiles. The parent div should use relative positioning
46 | and tile divs should be absolutely positioned.
47 |
48 | When the grid is drawn, the
49 | library looks at the width of the element and determines the number of cell columns
50 | that will fit given a minimum cell width. Once we have a grid of cells, the next
51 | step is to determine how to lay out the tiles on the grid.
52 |
53 |
54 | The simple, default template factory is named UniformTemplates. All of the tiles in the templates
55 | produced by this factory are the same size: 1x1s which each cover a single cell in the grid.
56 |
57 |
58 | Adjust the slider below to change the number of tiles displayed by the grid. Try resizing the
59 | browser window and watch the grid adjust the size and layout of the tiles. View the page source
60 | to see the code.
61 |
62 |
63 | Number of Tiles
64 |
65 |
66 |
67 |
68 |
Sample 2: Variable sized tiles using fixed templates
69 |
70 | The grid uses a pluggable template factory, which makes it possible to generate
71 | templates at run time. The grid will provide the factory with the number of columns
72 | and the target number of tiles. The template factory is expected to return a rectangular
73 | template defined by the number of columns, rows, and the set of rectangles specifying
74 | where each tile goes.
75 |
76 |
77 | The tile count is only a target because it may not be possible for
78 | some template factories to create a rectangular layout for a particular column + tile combination.
79 | If that happens, the factory should add extra tiles so that the template remains rectangular.
80 |
81 |
82 | For this example, we'll bypass the factory by setting the template directly. We've predefined 5 templates using JSON. Select any of the templates below to update the layout of the grid.
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
Sample 3: Updating Tiles During Resize
97 |
98 | In the previous sample we customized our grid by overriding tile creation and adding the tile number to the div. In this example, we'll create a custom Tile that can update content when the tile size changes.
99 |
100 |
101 | The size of a tile may change for several reasons:
102 |
103 |
The grid is resized
104 |
The template was changed
105 |
Tiles were added or removed
106 |
The width of a grid cell changed
107 |
108 |
109 |
110 | The grid determines the location and size of each tile based on the template and current
111 | set of tiles. However, it delegates the element changes by calling Tile.resize(). There are 5 parameters:
112 |
113 |
cellRect - A rectangle which specifies the location of the tile in the cellular grid. So if the first tile is a 1x1 then the cellRect would be { x: 0, y: 0, width: 1, height: 1 }.
114 |
pixelRect - A rectangle which specifies the location of the tile relative to the top left pixel coordinates of the grid. So if the first tile is 1x1 and the cell size is 200px then the pixelRect would be { x: 0, y: 0, width: 200, height: 200 }.
115 |
animate - Indicates whether the transition to the new location should be animated.
116 |
duration - Duration of the animation
117 |
onComplete - Callback fired once the tile has completed the move to the new location
118 |
119 |
120 |
121 | In this example, we'll override the resize method and render the cell size for each tile. Try switching between the following fixed templates and observe that the dimensions are updated when tiles are resized.
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/dist/tiles.js:
--------------------------------------------------------------------------------
1 | /*! Tiles.js v1.0.0 | http://thinkpixellab.com/tilesjs */
2 |
3 | // single namespace export
4 | var Tiles = {};
5 |
6 | (function($) {
7 |
8 | var Tile = Tiles.Tile = function(tileId, element) {
9 |
10 | this.id = tileId;
11 |
12 | // position and dimensions of tile inside the parent panel
13 | this.top = 0;
14 | this.left = 0;
15 | this.width = 0;
16 | this.height = 0;
17 |
18 | // cache the tile container element
19 | this.$el = $(element || document.createElement('div'));
20 | };
21 |
22 | Tile.prototype.appendTo = function($parent, fadeIn, delay, duration) {
23 | this.$el
24 | .hide()
25 | .appendTo($parent);
26 |
27 | if (fadeIn) {
28 | this.$el.delay(delay).fadeIn(duration);
29 | }
30 | else {
31 | this.$el.show();
32 | }
33 | };
34 |
35 | Tile.prototype.remove = function(animate, duration) {
36 | if (animate) {
37 | this.$el.fadeOut({
38 | complete: function() {
39 | $(this).remove();
40 | }
41 | });
42 | }
43 | else {
44 | this.$el.remove();
45 | }
46 | };
47 |
48 | // updates the tile layout with optional animation
49 | Tile.prototype.resize = function(cellRect, pixelRect, animate, duration, onComplete) {
50 |
51 | // store the list of needed changes
52 | var cssChanges = {},
53 | changed = false;
54 |
55 | // update position and dimensions
56 | if (this.left !== pixelRect.x) {
57 | cssChanges.left = pixelRect.x;
58 | this.left = pixelRect.x;
59 | changed = true;
60 | }
61 | if (this.top !== pixelRect.y) {
62 | cssChanges.top = pixelRect.y;
63 | this.top = pixelRect.y;
64 | changed = true;
65 | }
66 | if (this.width !== pixelRect.width) {
67 | cssChanges.width = pixelRect.width;
68 | this.width = pixelRect.width;
69 | changed = true;
70 | }
71 | if (this.height !== pixelRect.height) {
72 | cssChanges.height = pixelRect.height;
73 | this.height = pixelRect.height;
74 | changed = true;
75 | }
76 |
77 | // Sometimes animation fails to set the css top and left correctly
78 | // in webkit. We'll validate upon completion of the animation and
79 | // set the properties again if they don't match the expected values.
80 | var tile = this,
81 | validateChangesAndComplete = function() {
82 | var el = tile.$el[0];
83 | if (tile.left !== el.offsetLeft) {
84 | //console.log ('mismatch left:' + tile.left + ' actual:' + el.offsetLeft + ' id:' + tile.id);
85 | tile.$el.css('left', tile.left);
86 | }
87 | if (tile.top !== el.offsetTop) {
88 | //console.log ('mismatch top:' + tile.top + ' actual:' + el.offsetTop + ' id:' + tile.id);
89 | tile.$el.css('top', tile.top);
90 | }
91 |
92 | if (onComplete) {
93 | onComplete();
94 | }
95 | };
96 |
97 |
98 | // make css changes with animation when requested
99 | if (animate && changed) {
100 |
101 | this.$el.animate(cssChanges, {
102 | duration: duration,
103 | easing: 'swing',
104 | complete: validateChangesAndComplete
105 | });
106 | }
107 | else {
108 |
109 | if (changed) {
110 | this.$el.css(cssChanges);
111 | }
112 |
113 | setTimeout(validateChangesAndComplete, duration);
114 | }
115 | };
116 |
117 | })(jQuery);
118 |
119 |
120 | /*
121 | A grid template specifies the layout of variably sized tiles. A single
122 | cell tile should use the period character. Larger tiles may be created
123 | using any character that is unused by a adjacent tile. Whitespace is
124 | ignored when parsing the rows.
125 |
126 | Examples:
127 |
128 | var simpleTemplate = [
129 | ' A A . B ',
130 | ' A A . B ',
131 | ' . C C . ',
132 | ]
133 |
134 | var complexTemplate = [
135 | ' J J . . E E ',
136 | ' . A A . E E ',
137 | ' B A A F F . ',
138 | ' B . D D . H ',
139 | ' C C D D G H ',
140 | ' C C . . G . ',
141 | ];
142 | */
143 |
144 | (function($) {
145 |
146 | // remove whitespace and create 2d array
147 | var parseCells = function(rows) {
148 | var cells = [],
149 | numRows = rows.length,
150 | x, y, row, rowLength, cell;
151 |
152 | // parse each row
153 | for(y = 0; y < numRows; y++) {
154 |
155 | row = rows[y];
156 | cells[y] = [];
157 |
158 | // parse the cells in a single row
159 | for (x = 0, rowLength = row.length; x < rowLength; x++) {
160 | cell = row[x];
161 | if (cell !== ' ') {
162 | cells[y].push(cell);
163 | }
164 | }
165 | }
166 |
167 | // TODO: check to make sure the array isn't jagged
168 |
169 | return cells;
170 | };
171 |
172 | function Rectangle(x, y, width, height) {
173 | this.x = x;
174 | this.y = y;
175 | this.width = width;
176 | this.height = height;
177 | }
178 |
179 | Rectangle.prototype.copy = function() {
180 | return new Rectangle(this.x, this.y, this.width, this.height);
181 | };
182 |
183 | Tiles.Rectangle = Rectangle;
184 |
185 | // convert a 2d array of cell ids to a list of tile rects
186 | var parseRects = function(cells) {
187 | var rects = [],
188 | numRows = cells.length,
189 | numCols = numRows === 0 ? 0 : cells[0].length,
190 | cell, height, width, x, y, rectX, rectY;
191 |
192 | // make a copy of the cells that we can modify
193 | cells = cells.slice();
194 | for (y = 0; y < numRows; y++) {
195 | cells[y] = cells[y].slice();
196 | }
197 |
198 | // iterate through every cell and find rectangles
199 | for (y = 0; y < numRows; y++) {
200 | for(x = 0; x < numCols; x++) {
201 | cell = cells[y][x];
202 |
203 | // skip cells that are null
204 | if (cell == null) {
205 | continue;
206 | }
207 |
208 | width = 1;
209 | height = 1;
210 |
211 | if (cell !== Tiles.Template.SINGLE_CELL) {
212 |
213 | // find the width by going right until cell id no longer matches
214 | while(width + x < numCols &&
215 | cell === cells[y][x + width]) {
216 | width++;
217 | }
218 |
219 | // now find height by going down
220 | while (height + y < numRows &&
221 | cell === cells[y + height][x]) {
222 | height++;
223 | }
224 | }
225 |
226 | // null out all cells for the rect
227 | for(rectY = 0; rectY < height; rectY++) {
228 | for(rectX = 0; rectX < width; rectX++) {
229 | cells[y + rectY][x + rectX] = null;
230 | }
231 | }
232 |
233 | // add the rect
234 | rects.push(new Rectangle(x, y, width, height));
235 | }
236 | }
237 |
238 | return rects;
239 | };
240 |
241 | Tiles.Template = function(rects, numCols, numRows) {
242 | this.rects = rects;
243 | this.numTiles = this.rects.length;
244 | this.numRows = numRows;
245 | this.numCols = numCols;
246 | };
247 |
248 | Tiles.Template.prototype.copy = function() {
249 |
250 | var copyRects = [],
251 | len, i;
252 | for (i = 0, len = this.rects.length; i < len; i++) {
253 | copyRects.push(this.rects[i].copy());
254 | }
255 |
256 | return new Tiles.Template(copyRects, this.numCols, this.numRows);
257 | };
258 |
259 | // appends another template (assumes both are full rectangular grids)
260 | Tiles.Template.prototype.append = function(other) {
261 |
262 | if (this.numCols !== other.numCols) {
263 | throw 'Appended templates must have the same number of columns';
264 | }
265 |
266 | // new rects begin after the last current row
267 | var startY = this.numRows,
268 | i, len, rect;
269 |
270 | // copy rects from the other template
271 | for (i = 0, len = other.rects.length; i < len; i++) {
272 | rect = other.rects[i];
273 | this.rects.push(
274 | new Rectangle(rect.x, startY + rect.y, rect.width, rect.height));
275 | }
276 |
277 | this.numRows += other.numRows;
278 | this.numTiles += other.numTiles;
279 | };
280 |
281 | Tiles.Template.fromJSON = function(rows) {
282 | // convert rows to cells and then to rects
283 | var cells = parseCells(rows),
284 | rects = parseRects(cells);
285 | return new Tiles.Template(
286 | rects,
287 | cells.length > 0 ? cells[0].length : 0,
288 | cells.length);
289 | };
290 |
291 | Tiles.Template.prototype.toJSON = function() {
292 | // for now we'll assume 26 chars is enough (we don't solve graph coloring)
293 | var LABELS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
294 | NUM_LABELS = LABELS.length,
295 | labelIndex = 0,
296 | rows = [],
297 | i, len, rect, x, y, label;
298 |
299 | // fill in single tiles for each cell
300 | for (y = 0; y < this.numRows; y++) {
301 | rows[y] = [];
302 | for (x = 0; x < this.numCols; x++) {
303 | rows[y][x] = Tiles.Template.SINGLE_CELL;
304 | }
305 | }
306 |
307 | // now fill in bigger tiles
308 | for (i = 0, len = this.rects.length; i < len; i++) {
309 | rect = this.rects[i];
310 | if (rect.width > 1 || rect.height > 1) {
311 |
312 | // mark the tile position with a label
313 | label = LABELS[labelIndex];
314 | for(y = 0; y < rect.height; y++) {
315 | for(x = 0; x < rect.width; x++) {
316 | rows[rect.y + y][rect.x + x] = label;
317 | }
318 | }
319 |
320 | // advance the label index
321 | labelIndex = (labelIndex + 1) % NUM_LABELS;
322 | }
323 | }
324 |
325 | // turn the rows into strings
326 | for (y = 0; y < this.numRows; y++) {
327 | rows[y] = rows[y].join('');
328 | }
329 |
330 | return rows;
331 | };
332 |
333 | // period used to designate a single 1x1 cell tile
334 | Tiles.Template.SINGLE_CELL = '.';
335 |
336 | })(jQuery);
337 |
338 |
339 | // template provider which returns simple templates with 1x1 tiles
340 | Tiles.UniformTemplates = {
341 | get: function(numCols, targetTiles) {
342 | var numRows = Math.ceil(targetTiles / numCols),
343 | rects = [],
344 | x, y;
345 |
346 | // create the rects for 1x1 tiles
347 | for (y = 0; y < numRows; y++) {
348 | for (x = 0; x < numCols; x++) {
349 | rects.push(new Tiles.Rectangle(x, y, 1, 1));
350 | }
351 | }
352 |
353 | return new Tiles.Template(rects, numCols, numRows);
354 | }
355 | };
356 | (function($) {
357 |
358 | var Grid = Tiles.Grid = function(element) {
359 |
360 | this.$el = $(element);
361 |
362 | // animation lasts 500 ms by default
363 | this.animationDuration = 500;
364 |
365 | // the default set of factories used when creating templates
366 | this.templateFactory = Tiles.UniformTemplates;
367 |
368 | // defines the page size for prioritization of positions and tiles
369 | this.priorityPageSize = Number.MAX_VALUE;
370 |
371 | // spacing between tiles
372 | this.cellPadding = 10;
373 |
374 | // min width and height of a cell in the grid
375 | this.cellWidthMin = 150;
376 | this.cellHeightMin = 150;
377 |
378 | // actual width and height of a cell in the grid
379 | this.cellWidth = 0;
380 | this.cellHeight = 0;
381 |
382 | this.cellAspectRatio = 1;
383 |
384 | // number of tile cell columns
385 | this.numCols = 1;
386 |
387 | // cache the current template
388 | this.template = null;
389 |
390 | // flag that tracks whether a redraw is necessary
391 | this.isDirty = true;
392 |
393 | this.tiles = [];
394 |
395 | // keep track of added and removed tiles so we can update tiles
396 | // and the render the grid independently.
397 | this.tilesAdded = [];
398 | this.tilesRemoved = [];
399 | };
400 |
401 | Grid.prototype.getContentWidth = function() {
402 | // by default, the entire container width is used when drawing tiles
403 | return this.$el.width();
404 | };
405 |
406 | // gets the number of columns during a resize
407 | Grid.prototype.resizeColumns = function() {
408 | var panelWidth = this.getContentWidth();
409 |
410 | // ensure we have at least one column
411 | return Math.max(1, Math.floor((panelWidth + this.cellPadding) /
412 | (this.cellWidthMin + this.cellPadding)));
413 | };
414 |
415 | // gets the cell size during a grid resize
416 | Grid.prototype.resizeCellWidth = function() {
417 | var panelWidth = this.getContentWidth();
418 | return Math.ceil((panelWidth + this.cellPadding) / this.numCols) -
419 | this.cellPadding;
420 | };
421 |
422 | Grid.prototype.resize = function() {
423 |
424 | var newCols = this.resizeColumns();
425 | if (this.numCols !== newCols && newCols > 0) {
426 | this.numCols = newCols;
427 | this.isDirty = true;
428 | }
429 |
430 | var newCellWidth = this.resizeCellWidth();
431 | if (this.cellWidth !== newCellWidth && newCellWidth > 0) {
432 | this.cellWidth = newCellWidth;
433 | this.cellHeight = this.cellWidth / this.cellAspectRatio;
434 | this.isDirty = true;
435 | }
436 | };
437 |
438 | // refresh all tiles based on the current content
439 | Grid.prototype.updateTiles = function(newTileIds) {
440 |
441 | // ensure we dont have duplicate ids
442 | newTileIds = uniques(newTileIds);
443 |
444 | var numTiles = newTileIds.length,
445 | newTiles = [],
446 | i, tile, tileId, index;
447 |
448 | // retain existing tiles and queue remaining tiles for removal
449 | for (i = this.tiles.length - 1; i >= 0; i--) {
450 | tile = this.tiles[i];
451 | index = $.inArray(tile.id, newTileIds);
452 | if (index < 0) {
453 | this.tilesRemoved.push(tile);
454 | //console.log('Removing tile: ' + tile.id)
455 | }
456 | else {
457 | newTiles[index] = tile;
458 | }
459 | }
460 |
461 | // clear existing tiles
462 | this.tiles = [];
463 |
464 | // make sure we have tiles for new additions
465 | for (i = 0; i < numTiles; i++) {
466 |
467 | tile = newTiles[i];
468 | if (!tile) {
469 |
470 | tileId = newTileIds[i];
471 |
472 | // see if grid has a custom tile factory
473 | if (this.createTile) {
474 |
475 | tile = this.createTile(tileId);
476 |
477 | // skip the tile if it couldn't be created
478 | if (!tile) {
479 | //console.log('Tile element could not be created, id: ' + tileId);
480 | continue;
481 | }
482 |
483 | } else {
484 |
485 | tile = new Tiles.Tile(tileId);
486 | }
487 |
488 | // add tiles to queue (will be appended to DOM during redraw)
489 | this.tilesAdded.push(tile);
490 | //console.log('Adding tile: ' + tile.id);
491 | }
492 |
493 | this.tiles.push(tile);
494 | }
495 | };
496 |
497 | // helper to return unique items
498 | function uniques(items) {
499 | var results = [],
500 | numItems = items ? items.length : 0,
501 | i, item;
502 |
503 | for (i = 0; i < numItems; i++) {
504 | item = items[i];
505 | if ($.inArray(item, results) === -1) {
506 | results.push(item);
507 | }
508 | }
509 |
510 | return results;
511 | }
512 |
513 | // prepend new tiles
514 | Grid.prototype.insertTiles = function(newTileIds) {
515 | this.addTiles(newTileIds, true);
516 | };
517 |
518 | // append new tiles
519 | Grid.prototype.addTiles = function(newTileIds, prepend) {
520 |
521 | if (!newTileIds || newTileIds.length === 0) {
522 | return;
523 | }
524 |
525 | var prevTileIds = [],
526 | prevTileCount = this.tiles.length,
527 | i;
528 |
529 | // get the existing tile ids
530 | for (i = 0; i < prevTileCount; i++) {
531 | prevTileIds.push(this.tiles[i].id);
532 | }
533 |
534 | var tileIds = prepend ? newTileIds.concat(prevTileIds)
535 | : prevTileIds.concat(newTileIds);
536 | this.updateTiles(tileIds);
537 | };
538 |
539 | Grid.prototype.removeTiles = function(removeTileIds) {
540 |
541 | if (!removeTileIds || removeTileIds.length === 0) {
542 | return;
543 | }
544 |
545 | var updateTileIds = [],
546 | i, len, id;
547 |
548 | // get the set of ids which have not been removed
549 | for (i = 0, len = this.tiles.length; i < len; i++) {
550 | id = this.tiles[i].id;
551 | if ($.inArray(id, removeTileIds) === -1) {
552 | updateTileIds.push(id);
553 | }
554 | }
555 |
556 | this.updateTiles(updateTileIds);
557 | };
558 |
559 | Grid.prototype.createTemplate = function(numCols, targetTiles) {
560 |
561 | // ensure that we have at least one column
562 | numCols = Math.max(1, numCols);
563 |
564 | var template = this.templateFactory.get(numCols, targetTiles);
565 | if (!template) {
566 |
567 | // fallback in case the default factory can't generate a good template
568 | template = Tiles.UniformTemplates.get(numCols, targetTiles);
569 | }
570 |
571 | return template;
572 | };
573 |
574 | // ensures we have a good template for the specified numbef of tiles
575 | Grid.prototype.ensureTemplate = function(numTiles) {
576 |
577 | // verfiy that the current template is still valid
578 | if (!this.template || this.template.numCols !== this.numCols) {
579 | this.template = this.createTemplate(this.numCols, numTiles);
580 | this.isDirty = true;
581 | } else {
582 |
583 | // append another template if we don't have enough rects
584 | var missingRects = numTiles - this.template.rects.length;
585 | if (missingRects > 0) {
586 | this.template.append(
587 | this.createTemplate(this.numCols, missingRects));
588 | this.isDirty = true;
589 | }
590 |
591 | }
592 | };
593 |
594 | // helper that returns true if a tile was in the viewport or will be given
595 | // the new pixel rect coordinates and dimensions
596 | function wasOrWillBeVisible(viewRect, tile, newRect) {
597 |
598 | var viewMaxY = viewRect.y + viewRect.height,
599 | viewMaxX = viewRect.x + viewRect.width;
600 |
601 | // note: y axis is the more common exclusion, so check that first
602 |
603 | // was the tile visible?
604 | if (tile) {
605 | if (!((tile.top > viewMaxY) || (tile.top + tile.height < viewRect.y) ||
606 | (tile.left > viewMaxX) || (tile.left + tile.width < viewRect.x))) {
607 | return true;
608 | }
609 | }
610 |
611 | if (newRect) {
612 | // will it be visible?
613 | if (!((newRect.y > viewMaxY) || (newRect.y + newRect.height < viewRect.y) ||
614 | (newRect.x > viewMaxX) || (newRect.x + newRect.width < viewRect.x))) {
615 | return true;
616 | }
617 | }
618 |
619 | return false;
620 | }
621 |
622 | Grid.prototype.shouldRedraw = function() {
623 |
624 | // see if we need to calculate the cell size
625 | if (this.cellWidth <= 0) {
626 | this.resize();
627 | }
628 |
629 | // verify that we have a template
630 | this.ensureTemplate(this.tiles.length);
631 |
632 | // only redraw when necessary
633 | var shouldRedraw = (this.isDirty ||
634 | this.tilesAdded.length > 0 ||
635 | this.tilesRemoved.length > 0);
636 |
637 | return shouldRedraw;
638 | };
639 |
640 | // converts cell rectangles to pixel rectangles. allows users
641 | // to override exact placement of the tiles.
642 | Grid.prototype.getPixelRectangle = function(cellRect) {
643 |
644 | var widthPlusPadding = this.cellWidth + this.cellPadding,
645 | heightPlusPadding = this.cellHeight + this.cellPadding;
646 |
647 | return new Tiles.Rectangle(
648 | cellRect.x * widthPlusPadding,
649 | cellRect.y * heightPlusPadding,
650 | (cellRect.width * widthPlusPadding) - this.cellPadding,
651 | (cellRect.height * heightPlusPadding) - this.cellPadding);
652 | };
653 |
654 | // redraws the grid after tile collection changes
655 | Grid.prototype.redraw = function(animate, onComplete) {
656 |
657 | // see if we should redraw
658 | if (!this.shouldRedraw()) {
659 | if (onComplete) {
660 | onComplete(false); // tell callback that we did not redraw
661 | }
662 | return;
663 | }
664 |
665 | var numTiles = this.tiles.length,
666 | pageSize = this.priorityPageSize,
667 | duration = this.animationDuration,
668 | tileIndex = 0,
669 | appendDelay = 0,
670 | maxAppendDelay = 0,
671 | viewRect = new Tiles.Rectangle(
672 | this.$el.scrollLeft(),
673 | this.$el.scrollTop(),
674 | this.$el.width(),
675 | this.$el.height()),
676 | tile, added, pageRects, pageTiles, i, len, cellRect, pixelRect,
677 | animateTile, priorityRects, priorityTiles;
678 |
679 |
680 | // chunk tile layout by pages which are internally prioritized
681 | for (tileIndex = 0; tileIndex < numTiles; tileIndex += pageSize) {
682 |
683 | // get the next page of rects and tiles
684 | pageRects = this.template.rects.slice(tileIndex, tileIndex + pageSize);
685 | pageTiles = this.tiles.slice(tileIndex, tileIndex + pageSize);
686 |
687 | // create a copy that can be ordered
688 | priorityRects = pageRects.slice(0);
689 | priorityTiles = pageTiles.slice(0);
690 |
691 | // prioritize the page of rects and tiles
692 | if (this.prioritizePage) {
693 | this.prioritizePage(priorityRects, priorityTiles);
694 | }
695 |
696 | // place all the tiles for the current page
697 | for (i = 0, len = priorityTiles.length; i < len; i++) {
698 | tile = priorityTiles[i];
699 | added = $.inArray(tile, this.tilesAdded) >= 0;
700 |
701 | cellRect = priorityRects[i];
702 | pixelRect = this.getPixelRectangle(cellRect);
703 |
704 | tile.resize(
705 | cellRect,
706 | pixelRect,
707 | animate && !added && wasOrWillBeVisible(viewRect, tile, pixelRect),
708 | duration);
709 |
710 | if (added) {
711 |
712 | // decide whether to animate (fadeIn) and get the duration
713 | animateTile = animate && wasOrWillBeVisible(viewRect, null, pixelRect);
714 | if (animateTile && this.getAppendDelay) {
715 | appendDelay = this.getAppendDelay(
716 | cellRect, pageRects, priorityRects,
717 | tile, pageTiles, priorityTiles);
718 | maxAppendDelay = Math.max(maxAppendDelay, appendDelay) || 0;
719 | } else {
720 | appendDelay = 0;
721 | }
722 |
723 | tile.appendTo(this.$el, animateTile, appendDelay, duration);
724 | }
725 | }
726 | }
727 |
728 | // fade out all removed tiles
729 | for (i = 0, len = this.tilesRemoved.length; i < len; i++) {
730 | tile = this.tilesRemoved[i];
731 | animateTile = animate && wasOrWillBeVisible(viewRect, tile, null);
732 | tile.remove(animateTile, duration);
733 | }
734 |
735 | // clear pending queues for add / remove
736 | this.tilesRemoved = [];
737 | this.tilesAdded = [];
738 | this.isDirty = false;
739 |
740 | if (onComplete) {
741 | setTimeout(
742 | function() { onComplete(true); },
743 | Math.max(maxAppendDelay, duration) + 10
744 | );
745 | }
746 | };
747 |
748 | })(jQuery);
749 |
--------------------------------------------------------------------------------
/dist/tiles.min.js:
--------------------------------------------------------------------------------
1 | /*! Tiles.js v1.0.0 | http://thinkpixellab.com/tilesjs */
2 | var Tiles={};!function(a){var b=Tiles.Tile=function(b,c){this.id=b,this.top=0,this.left=0,this.width=0,this.height=0,this.$el=a(c||document.createElement("div"))};b.prototype.appendTo=function(a,b,c,d){this.$el.hide().appendTo(a),b?this.$el.delay(c).fadeIn(d):this.$el.show()},b.prototype.remove=function(b,c){b?this.$el.fadeOut({complete:function(){a(this).remove()}}):this.$el.remove()},b.prototype.resize=function(a,b,c,d,e){var f={},g=!1;this.left!==b.x&&(f.left=b.x,this.left=b.x,g=!0),this.top!==b.y&&(f.top=b.y,this.top=b.y,g=!0),this.width!==b.width&&(f.width=b.width,this.width=b.width,g=!0),this.height!==b.height&&(f.height=b.height,this.height=b.height,g=!0);var h=this,i=function(){var a=h.$el[0];h.left!==a.offsetLeft&&h.$el.css("left",h.left),h.top!==a.offsetTop&&h.$el.css("top",h.top),e&&e()};c&&g?this.$el.animate(f,{duration:d,easing:"swing",complete:i}):(g&&this.$el.css(f),setTimeout(i,d))}}(jQuery),function(a){function b(a,b,c,d){this.x=a,this.y=b,this.width=c,this.height=d}var c=function(a){var b,c,d,e,f,g=[],h=a.length;for(c=0;h>c;c++)for(d=a[c],g[c]=[],b=0,e=d.length;e>b;b++)f=d[b]," "!==f&&g[c].push(f);return g};b.prototype.copy=function(){return new b(this.x,this.y,this.width,this.height)},Tiles.Rectangle=b;var d=function(a){var c,d,e,f,g,h,i,j=[],k=a.length,l=0===k?0:a[0].length;for(a=a.slice(),g=0;k>g;g++)a[g]=a[g].slice();for(g=0;k>g;g++)for(f=0;l>f;f++)if(c=a[g][f],null!=c){if(e=1,d=1,c!==Tiles.Template.SINGLE_CELL){for(;l>e+f&&c===a[g][f+e];)e++;for(;k>d+g&&c===a[g+d][f];)d++}for(i=0;d>i;i++)for(h=0;e>h;h++)a[g+i][f+h]=null;j.push(new b(f,g,e,d))}return j};Tiles.Template=function(a,b,c){this.rects=a,this.numTiles=this.rects.length,this.numRows=c,this.numCols=b},Tiles.Template.prototype.copy=function(){var a,b,c=[];for(b=0,a=this.rects.length;a>b;b++)c.push(this.rects[b].copy());return new Tiles.Template(c,this.numCols,this.numRows)},Tiles.Template.prototype.append=function(a){if(this.numCols!==a.numCols)throw"Appended templates must have the same number of columns";var c,d,e,f=this.numRows;for(c=0,d=a.rects.length;d>c;c++)e=a.rects[c],this.rects.push(new b(e.x,f+e.y,e.width,e.height));this.numRows+=a.numRows,this.numTiles+=a.numTiles},Tiles.Template.fromJSON=function(a){var b=c(a),e=d(b);return new Tiles.Template(e,b.length>0?b[0].length:0,b.length)},Tiles.Template.prototype.toJSON=function(){var a,b,c,d,e,f,g="ABCDEFGHIJKLMNOPQRSTUVWXYZ",h=g.length,i=0,j=[];for(e=0;ea;a++)if(c=this.rects[a],c.width>1||c.height>1){for(f=g[i],e=0;ed;d++)for(c=0;a>c;c++)f.push(new Tiles.Rectangle(c,d,1,1));return new Tiles.Template(f,a,e)}},function(a){function b(b){var c,d,e=[],f=b?b.length:0;for(c=0;f>c;c++)d=b[c],-1===a.inArray(d,e)&&e.push(d);return e}function c(a,b,c){var d=a.y+a.height,e=a.x+a.width;return b&&!(b.top>d||b.top+b.heighte||b.left+b.widthd||c.y+c.heighte||c.x+c.width0&&(this.numCols=a,this.isDirty=!0);var b=this.resizeCellWidth();this.cellWidth!==b&&b>0&&(this.cellWidth=b,this.cellHeight=this.cellWidth/this.cellAspectRatio,this.isDirty=!0)},d.prototype.updateTiles=function(c){c=b(c);var d,e,f,g,h=c.length,i=[];for(d=this.tiles.length-1;d>=0;d--)e=this.tiles[d],g=a.inArray(e.id,c),0>g?this.tilesRemoved.push(e):i[g]=e;for(this.tiles=[],d=0;h>d;d++){if(e=i[d],!e){if(f=c[d],this.createTile){if(e=this.createTile(f),!e)continue}else e=new Tiles.Tile(f);this.tilesAdded.push(e)}this.tiles.push(e)}},d.prototype.insertTiles=function(a){this.addTiles(a,!0)},d.prototype.addTiles=function(a,b){if(a&&0!==a.length){var c,d=[],e=this.tiles.length;for(c=0;e>c;c++)d.push(this.tiles[c].id);var f=b?a.concat(d):d.concat(a);this.updateTiles(f)}},d.prototype.removeTiles=function(b){if(b&&0!==b.length){var c,d,e,f=[];for(c=0,d=this.tiles.length;d>c;c++)e=this.tiles[c].id,-1===a.inArray(e,b)&&f.push(e);this.updateTiles(f)}},d.prototype.createTemplate=function(a,b){a=Math.max(1,a);var c=this.templateFactory.get(a,b);return c||(c=Tiles.UniformTemplates.get(a,b)),c},d.prototype.ensureTemplate=function(a){if(this.template&&this.template.numCols===this.numCols){var b=a-this.template.rects.length;b>0&&(this.template.append(this.createTemplate(this.numCols,b)),this.isDirty=!0)}else this.template=this.createTemplate(this.numCols,a),this.isDirty=!0},d.prototype.shouldRedraw=function(){this.cellWidth<=0&&this.resize(),this.ensureTemplate(this.tiles.length);var a=this.isDirty||this.tilesAdded.length>0||this.tilesRemoved.length>0;return a},d.prototype.getPixelRectangle=function(a){var b=this.cellWidth+this.cellPadding,c=this.cellHeight+this.cellPadding;return new Tiles.Rectangle(a.x*b,a.y*c,a.width*b-this.cellPadding,a.height*c-this.cellPadding)},d.prototype.redraw=function(b,d){if(!this.shouldRedraw())return void(d&&d(!1));var e,f,g,h,i,j,k,l,m,n,o,p=this.tiles.length,q=this.priorityPageSize,r=this.animationDuration,s=0,t=0,u=0,v=new Tiles.Rectangle(this.$el.scrollLeft(),this.$el.scrollTop(),this.$el.width(),this.$el.height());for(s=0;p>s;s+=q)for(g=this.template.rects.slice(s,s+q),h=this.tiles.slice(s,s+q),n=g.slice(0),o=h.slice(0),this.prioritizePage&&this.prioritizePage(n,o),i=0,j=o.length;j>i;i++)e=o[i],f=a.inArray(e,this.tilesAdded)>=0,k=n[i],l=this.getPixelRectangle(k),e.resize(k,l,b&&!f&&c(v,e,l),r),f&&(m=b&&c(v,null,l),m&&this.getAppendDelay?(t=this.getAppendDelay(k,g,n,e,h,o),u=Math.max(u,t)||0):t=0,e.appendTo(this.$el,m,t,r));for(i=0,j=this.tilesRemoved.length;j>i;i++)e=this.tilesRemoved[i],m=b&&c(v,e,null),e.remove(m,r);this.tilesRemoved=[],this.tilesAdded=[],this.isDirty=!1,d&&setTimeout(function(){d(!0)},Math.max(u,r)+10)}}(jQuery);
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function(grunt) {
3 |
4 | var BANNER_TEMPLATE = '/*! <%= pkg.title %> v<%= pkg.version %> | <%= pkg.homepage %> */\n';
5 |
6 | // Project configuration.
7 | grunt.initConfig({
8 | pkg: grunt.file.readJSON('package.json'),
9 | concat: {
10 | options: {
11 | banner: BANNER_TEMPLATE
12 | },
13 | dist: {
14 | src: [
15 | 'src/Tile.js',
16 | 'src/Template.js',
17 | 'src/UniformTemplates.js',
18 | 'src/Grid.js'
19 | ],
20 | dest: 'dist/tiles.js'
21 | }
22 | },
23 | jshint: {
24 | files: [ 'gruntfile.js', 'src/*.js' ],
25 | options: {
26 | curly: true,
27 | eqeqeq: true,
28 | immed: true,
29 | latedef: true,
30 | newcap: true,
31 | noarg: true,
32 | sub: true,
33 | undef: true,
34 | boss: true,
35 | eqnull: true,
36 | browser: true,
37 | globals: {
38 | jQuery: true,
39 | Tiles: true,
40 | console: true
41 | }
42 | }
43 | },
44 | watch: {
45 | cj: {
46 | files: ['<%= jshint.files %>'],
47 | tasks: ['jshint', 'concat', 'copy', 'uglfiy']
48 | }
49 | },
50 | uglify: {
51 | options: {
52 | banner: BANNER_TEMPLATE
53 | },
54 | dist: {
55 | src: ['<%= concat.dist.dest %>'],
56 | dest: 'dist/tiles.min.js'
57 | }
58 | }
59 | });
60 |
61 | grunt.loadNpmTasks('grunt-contrib-uglify');
62 | grunt.loadNpmTasks('grunt-contrib-jshint');
63 | grunt.loadNpmTasks('grunt-contrib-watch');
64 | grunt.loadNpmTasks('grunt-contrib-concat');
65 | grunt.loadNpmTasks('grunt-contrib-copy');
66 | grunt.loadNpmTasks('grunt-contrib-clean');
67 |
68 | grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
69 |
70 | };
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tilesjs",
3 | "title": "Tiles.js",
4 | "version": "1.0.1",
5 | "homepage": "http://thinkpixellab.com/tilesjs",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Pixel Lab",
9 | "url": "http://thinkpixellab.com"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "http://github.com/thinkpixellab/tilesjs.git"
14 | },
15 | "dependencies": {
16 | "grunt": "~0.4.5",
17 | "grunt-contrib-uglify": "~0.9.1",
18 | "grunt-contrib-jshint": "~0.11.2",
19 | "grunt-contrib-watch": "~0.6.1",
20 | "grunt-contrib-concat": "~0.5.1",
21 | "grunt-contrib-copy": "~0.8.0",
22 | "grunt-contrib-clean": "~0.6.0"
23 | },
24 | "devDependencies": {},
25 | "keywords": []
26 | }
27 |
--------------------------------------------------------------------------------
/src/Grid.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 |
3 | var Grid = Tiles.Grid = function(element) {
4 |
5 | this.$el = $(element);
6 |
7 | // animation lasts 500 ms by default
8 | this.animationDuration = 500;
9 |
10 | // the default set of factories used when creating templates
11 | this.templateFactory = Tiles.UniformTemplates;
12 |
13 | // defines the page size for prioritization of positions and tiles
14 | this.priorityPageSize = Number.MAX_VALUE;
15 |
16 | // spacing between tiles
17 | this.cellPadding = 10;
18 |
19 | // min width and height of a cell in the grid
20 | this.cellWidthMin = 150;
21 | this.cellHeightMin = 150;
22 |
23 | // actual width and height of a cell in the grid
24 | this.cellWidth = 0;
25 | this.cellHeight = 0;
26 |
27 | this.cellAspectRatio = 1;
28 |
29 | // number of tile cell columns
30 | this.numCols = 1;
31 |
32 | // cache the current template
33 | this.template = null;
34 |
35 | // flag that tracks whether a redraw is necessary
36 | this.isDirty = true;
37 |
38 | this.tiles = [];
39 |
40 | // keep track of added and removed tiles so we can update tiles
41 | // and the render the grid independently.
42 | this.tilesAdded = [];
43 | this.tilesRemoved = [];
44 | };
45 |
46 | Grid.prototype.getContentWidth = function() {
47 | // by default, the entire container width is used when drawing tiles
48 | return this.$el.width();
49 | };
50 |
51 | // gets the number of columns during a resize
52 | Grid.prototype.resizeColumns = function() {
53 | var panelWidth = this.getContentWidth();
54 |
55 | // ensure we have at least one column
56 | return Math.max(1, Math.floor((panelWidth + this.cellPadding) /
57 | (this.cellWidthMin + this.cellPadding)));
58 | };
59 |
60 | // gets the cell size during a grid resize
61 | Grid.prototype.resizeCellWidth = function() {
62 | var panelWidth = this.getContentWidth();
63 | return Math.ceil((panelWidth + this.cellPadding) / this.numCols) -
64 | this.cellPadding;
65 | };
66 |
67 | Grid.prototype.resize = function() {
68 |
69 | var newCols = this.resizeColumns();
70 | if (this.numCols !== newCols && newCols > 0) {
71 | this.numCols = newCols;
72 | this.isDirty = true;
73 | }
74 |
75 | var newCellWidth = this.resizeCellWidth();
76 | if (this.cellWidth !== newCellWidth && newCellWidth > 0) {
77 | this.cellWidth = newCellWidth;
78 | this.cellHeight = this.cellWidth / this.cellAspectRatio;
79 | this.isDirty = true;
80 | }
81 | };
82 |
83 | // refresh all tiles based on the current content
84 | Grid.prototype.updateTiles = function(newTileIds) {
85 |
86 | // ensure we dont have duplicate ids
87 | newTileIds = uniques(newTileIds);
88 |
89 | var numTiles = newTileIds.length,
90 | newTiles = [],
91 | i, tile, tileId, index;
92 |
93 | // retain existing tiles and queue remaining tiles for removal
94 | for (i = this.tiles.length - 1; i >= 0; i--) {
95 | tile = this.tiles[i];
96 | index = $.inArray(tile.id, newTileIds);
97 | if (index < 0) {
98 | this.tilesRemoved.push(tile);
99 | //console.log('Removing tile: ' + tile.id)
100 | }
101 | else {
102 | newTiles[index] = tile;
103 | }
104 | }
105 |
106 | // clear existing tiles
107 | this.tiles = [];
108 |
109 | // make sure we have tiles for new additions
110 | for (i = 0; i < numTiles; i++) {
111 |
112 | tile = newTiles[i];
113 | if (!tile) {
114 |
115 | tileId = newTileIds[i];
116 |
117 | // see if grid has a custom tile factory
118 | if (this.createTile) {
119 |
120 | tile = this.createTile(tileId);
121 |
122 | // skip the tile if it couldn't be created
123 | if (!tile) {
124 | //console.log('Tile element could not be created, id: ' + tileId);
125 | continue;
126 | }
127 |
128 | } else {
129 |
130 | tile = new Tiles.Tile(tileId);
131 | }
132 |
133 | // add tiles to queue (will be appended to DOM during redraw)
134 | this.tilesAdded.push(tile);
135 | //console.log('Adding tile: ' + tile.id);
136 | }
137 |
138 | this.tiles.push(tile);
139 | }
140 | };
141 |
142 | // helper to return unique items
143 | function uniques(items) {
144 | var results = [],
145 | numItems = items ? items.length : 0,
146 | i, item;
147 |
148 | for (i = 0; i < numItems; i++) {
149 | item = items[i];
150 | if ($.inArray(item, results) === -1) {
151 | results.push(item);
152 | }
153 | }
154 |
155 | return results;
156 | }
157 |
158 | // prepend new tiles
159 | Grid.prototype.insertTiles = function(newTileIds) {
160 | this.addTiles(newTileIds, true);
161 | };
162 |
163 | // append new tiles
164 | Grid.prototype.addTiles = function(newTileIds, prepend) {
165 |
166 | if (!newTileIds || newTileIds.length === 0) {
167 | return;
168 | }
169 |
170 | var prevTileIds = [],
171 | prevTileCount = this.tiles.length,
172 | i;
173 |
174 | // get the existing tile ids
175 | for (i = 0; i < prevTileCount; i++) {
176 | prevTileIds.push(this.tiles[i].id);
177 | }
178 |
179 | var tileIds = prepend ? newTileIds.concat(prevTileIds)
180 | : prevTileIds.concat(newTileIds);
181 | this.updateTiles(tileIds);
182 | };
183 |
184 | Grid.prototype.removeTiles = function(removeTileIds) {
185 |
186 | if (!removeTileIds || removeTileIds.length === 0) {
187 | return;
188 | }
189 |
190 | var updateTileIds = [],
191 | i, len, id;
192 |
193 | // get the set of ids which have not been removed
194 | for (i = 0, len = this.tiles.length; i < len; i++) {
195 | id = this.tiles[i].id;
196 | if ($.inArray(id, removeTileIds) === -1) {
197 | updateTileIds.push(id);
198 | }
199 | }
200 |
201 | this.updateTiles(updateTileIds);
202 | };
203 |
204 | Grid.prototype.createTemplate = function(numCols, targetTiles) {
205 |
206 | // ensure that we have at least one column
207 | numCols = Math.max(1, numCols);
208 |
209 | var template = this.templateFactory.get(numCols, targetTiles);
210 | if (!template) {
211 |
212 | // fallback in case the default factory can't generate a good template
213 | template = Tiles.UniformTemplates.get(numCols, targetTiles);
214 | }
215 |
216 | return template;
217 | };
218 |
219 | // ensures we have a good template for the specified numbef of tiles
220 | Grid.prototype.ensureTemplate = function(numTiles) {
221 |
222 | // verfiy that the current template is still valid
223 | if (!this.template || this.template.numCols !== this.numCols) {
224 | this.template = this.createTemplate(this.numCols, numTiles);
225 | this.isDirty = true;
226 | } else {
227 |
228 | // append another template if we don't have enough rects
229 | var missingRects = numTiles - this.template.rects.length;
230 | if (missingRects > 0) {
231 | this.template.append(
232 | this.createTemplate(this.numCols, missingRects));
233 | this.isDirty = true;
234 | }
235 |
236 | }
237 | };
238 |
239 | // helper that returns true if a tile was in the viewport or will be given
240 | // the new pixel rect coordinates and dimensions
241 | function wasOrWillBeVisible(viewRect, tile, newRect) {
242 |
243 | var viewMaxY = viewRect.y + viewRect.height,
244 | viewMaxX = viewRect.x + viewRect.width;
245 |
246 | // note: y axis is the more common exclusion, so check that first
247 |
248 | // was the tile visible?
249 | if (tile) {
250 | if (!((tile.top > viewMaxY) || (tile.top + tile.height < viewRect.y) ||
251 | (tile.left > viewMaxX) || (tile.left + tile.width < viewRect.x))) {
252 | return true;
253 | }
254 | }
255 |
256 | if (newRect) {
257 | // will it be visible?
258 | if (!((newRect.y > viewMaxY) || (newRect.y + newRect.height < viewRect.y) ||
259 | (newRect.x > viewMaxX) || (newRect.x + newRect.width < viewRect.x))) {
260 | return true;
261 | }
262 | }
263 |
264 | return false;
265 | }
266 |
267 | Grid.prototype.shouldRedraw = function() {
268 |
269 | // see if we need to calculate the cell size
270 | if (this.cellWidth <= 0) {
271 | this.resize();
272 | }
273 |
274 | // verify that we have a template
275 | this.ensureTemplate(this.tiles.length);
276 |
277 | // only redraw when necessary
278 | var shouldRedraw = (this.isDirty ||
279 | this.tilesAdded.length > 0 ||
280 | this.tilesRemoved.length > 0);
281 |
282 | return shouldRedraw;
283 | };
284 |
285 | // converts cell rectangles to pixel rectangles. allows users
286 | // to override exact placement of the tiles.
287 | Grid.prototype.getPixelRectangle = function(cellRect) {
288 |
289 | var widthPlusPadding = this.cellWidth + this.cellPadding,
290 | heightPlusPadding = this.cellHeight + this.cellPadding;
291 |
292 | return new Tiles.Rectangle(
293 | cellRect.x * widthPlusPadding,
294 | cellRect.y * heightPlusPadding,
295 | (cellRect.width * widthPlusPadding) - this.cellPadding,
296 | (cellRect.height * heightPlusPadding) - this.cellPadding);
297 | };
298 |
299 | // redraws the grid after tile collection changes
300 | Grid.prototype.redraw = function(animate, onComplete) {
301 |
302 | // see if we should redraw
303 | if (!this.shouldRedraw()) {
304 | if (onComplete) {
305 | onComplete(false); // tell callback that we did not redraw
306 | }
307 | return;
308 | }
309 |
310 | var numTiles = this.tiles.length,
311 | pageSize = this.priorityPageSize,
312 | duration = this.animationDuration,
313 | tileIndex = 0,
314 | appendDelay = 0,
315 | maxAppendDelay = 0,
316 | viewRect = new Tiles.Rectangle(
317 | this.$el.scrollLeft(),
318 | this.$el.scrollTop(),
319 | this.$el.width(),
320 | this.$el.height()),
321 | tile, added, pageRects, pageTiles, i, len, cellRect, pixelRect,
322 | animateTile, priorityRects, priorityTiles;
323 |
324 |
325 | // chunk tile layout by pages which are internally prioritized
326 | for (tileIndex = 0; tileIndex < numTiles; tileIndex += pageSize) {
327 |
328 | // get the next page of rects and tiles
329 | pageRects = this.template.rects.slice(tileIndex, tileIndex + pageSize);
330 | pageTiles = this.tiles.slice(tileIndex, tileIndex + pageSize);
331 |
332 | // create a copy that can be ordered
333 | priorityRects = pageRects.slice(0);
334 | priorityTiles = pageTiles.slice(0);
335 |
336 | // prioritize the page of rects and tiles
337 | if (this.prioritizePage) {
338 | this.prioritizePage(priorityRects, priorityTiles);
339 | }
340 |
341 | // place all the tiles for the current page
342 | for (i = 0, len = priorityTiles.length; i < len; i++) {
343 | tile = priorityTiles[i];
344 | added = $.inArray(tile, this.tilesAdded) >= 0;
345 |
346 | cellRect = priorityRects[i];
347 | pixelRect = this.getPixelRectangle(cellRect);
348 |
349 | tile.resize(
350 | cellRect,
351 | pixelRect,
352 | animate && !added && wasOrWillBeVisible(viewRect, tile, pixelRect),
353 | duration);
354 |
355 | if (added) {
356 |
357 | // decide whether to animate (fadeIn) and get the duration
358 | animateTile = animate && wasOrWillBeVisible(viewRect, null, pixelRect);
359 | if (animateTile && this.getAppendDelay) {
360 | appendDelay = this.getAppendDelay(
361 | cellRect, pageRects, priorityRects,
362 | tile, pageTiles, priorityTiles);
363 | maxAppendDelay = Math.max(maxAppendDelay, appendDelay) || 0;
364 | } else {
365 | appendDelay = 0;
366 | }
367 |
368 | tile.appendTo(this.$el, animateTile, appendDelay, duration);
369 | }
370 | }
371 | }
372 |
373 | // fade out all removed tiles
374 | for (i = 0, len = this.tilesRemoved.length; i < len; i++) {
375 | tile = this.tilesRemoved[i];
376 | animateTile = animate && wasOrWillBeVisible(viewRect, tile, null);
377 | tile.remove(animateTile, duration);
378 | }
379 |
380 | // clear pending queues for add / remove
381 | this.tilesRemoved = [];
382 | this.tilesAdded = [];
383 | this.isDirty = false;
384 |
385 | if (onComplete) {
386 | setTimeout(
387 | function() { onComplete(true); },
388 | Math.max(maxAppendDelay, duration) + 10
389 | );
390 | }
391 | };
392 |
393 | })(jQuery);
394 |
--------------------------------------------------------------------------------
/src/Template.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | A grid template specifies the layout of variably sized tiles. A single
4 | cell tile should use the period character. Larger tiles may be created
5 | using any character that is unused by a adjacent tile. Whitespace is
6 | ignored when parsing the rows.
7 |
8 | Examples:
9 |
10 | var simpleTemplate = [
11 | ' A A . B ',
12 | ' A A . B ',
13 | ' . C C . ',
14 | ]
15 |
16 | var complexTemplate = [
17 | ' J J . . E E ',
18 | ' . A A . E E ',
19 | ' B A A F F . ',
20 | ' B . D D . H ',
21 | ' C C D D G H ',
22 | ' C C . . G . ',
23 | ];
24 | */
25 |
26 | (function($) {
27 |
28 | // remove whitespace and create 2d array
29 | var parseCells = function(rows) {
30 | var cells = [],
31 | numRows = rows.length,
32 | x, y, row, rowLength, cell;
33 |
34 | // parse each row
35 | for(y = 0; y < numRows; y++) {
36 |
37 | row = rows[y];
38 | cells[y] = [];
39 |
40 | // parse the cells in a single row
41 | for (x = 0, rowLength = row.length; x < rowLength; x++) {
42 | cell = row[x];
43 | if (cell !== ' ') {
44 | cells[y].push(cell);
45 | }
46 | }
47 | }
48 |
49 | // TODO: check to make sure the array isn't jagged
50 |
51 | return cells;
52 | };
53 |
54 | function Rectangle(x, y, width, height) {
55 | this.x = x;
56 | this.y = y;
57 | this.width = width;
58 | this.height = height;
59 | }
60 |
61 | Rectangle.prototype.copy = function() {
62 | return new Rectangle(this.x, this.y, this.width, this.height);
63 | };
64 |
65 | Tiles.Rectangle = Rectangle;
66 |
67 | // convert a 2d array of cell ids to a list of tile rects
68 | var parseRects = function(cells) {
69 | var rects = [],
70 | numRows = cells.length,
71 | numCols = numRows === 0 ? 0 : cells[0].length,
72 | cell, height, width, x, y, rectX, rectY;
73 |
74 | // make a copy of the cells that we can modify
75 | cells = cells.slice();
76 | for (y = 0; y < numRows; y++) {
77 | cells[y] = cells[y].slice();
78 | }
79 |
80 | // iterate through every cell and find rectangles
81 | for (y = 0; y < numRows; y++) {
82 | for(x = 0; x < numCols; x++) {
83 | cell = cells[y][x];
84 |
85 | // skip cells that are null
86 | if (cell == null) {
87 | continue;
88 | }
89 |
90 | width = 1;
91 | height = 1;
92 |
93 | if (cell !== Tiles.Template.SINGLE_CELL) {
94 |
95 | // find the width by going right until cell id no longer matches
96 | while(width + x < numCols &&
97 | cell === cells[y][x + width]) {
98 | width++;
99 | }
100 |
101 | // now find height by going down
102 | while (height + y < numRows &&
103 | cell === cells[y + height][x]) {
104 | height++;
105 | }
106 | }
107 |
108 | // null out all cells for the rect
109 | for(rectY = 0; rectY < height; rectY++) {
110 | for(rectX = 0; rectX < width; rectX++) {
111 | cells[y + rectY][x + rectX] = null;
112 | }
113 | }
114 |
115 | // add the rect
116 | rects.push(new Rectangle(x, y, width, height));
117 | }
118 | }
119 |
120 | return rects;
121 | };
122 |
123 | Tiles.Template = function(rects, numCols, numRows) {
124 | this.rects = rects;
125 | this.numTiles = this.rects.length;
126 | this.numRows = numRows;
127 | this.numCols = numCols;
128 | };
129 |
130 | Tiles.Template.prototype.copy = function() {
131 |
132 | var copyRects = [],
133 | len, i;
134 | for (i = 0, len = this.rects.length; i < len; i++) {
135 | copyRects.push(this.rects[i].copy());
136 | }
137 |
138 | return new Tiles.Template(copyRects, this.numCols, this.numRows);
139 | };
140 |
141 | // appends another template (assumes both are full rectangular grids)
142 | Tiles.Template.prototype.append = function(other) {
143 |
144 | if (this.numCols !== other.numCols) {
145 | throw 'Appended templates must have the same number of columns';
146 | }
147 |
148 | // new rects begin after the last current row
149 | var startY = this.numRows,
150 | i, len, rect;
151 |
152 | // copy rects from the other template
153 | for (i = 0, len = other.rects.length; i < len; i++) {
154 | rect = other.rects[i];
155 | this.rects.push(
156 | new Rectangle(rect.x, startY + rect.y, rect.width, rect.height));
157 | }
158 |
159 | this.numRows += other.numRows;
160 | this.numTiles += other.numTiles;
161 | };
162 |
163 | Tiles.Template.fromJSON = function(rows) {
164 | // convert rows to cells and then to rects
165 | var cells = parseCells(rows),
166 | rects = parseRects(cells);
167 | return new Tiles.Template(
168 | rects,
169 | cells.length > 0 ? cells[0].length : 0,
170 | cells.length);
171 | };
172 |
173 | Tiles.Template.prototype.toJSON = function() {
174 | // for now we'll assume 26 chars is enough (we don't solve graph coloring)
175 | var LABELS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
176 | NUM_LABELS = LABELS.length,
177 | labelIndex = 0,
178 | rows = [],
179 | i, len, rect, x, y, label;
180 |
181 | // fill in single tiles for each cell
182 | for (y = 0; y < this.numRows; y++) {
183 | rows[y] = [];
184 | for (x = 0; x < this.numCols; x++) {
185 | rows[y][x] = Tiles.Template.SINGLE_CELL;
186 | }
187 | }
188 |
189 | // now fill in bigger tiles
190 | for (i = 0, len = this.rects.length; i < len; i++) {
191 | rect = this.rects[i];
192 | if (rect.width > 1 || rect.height > 1) {
193 |
194 | // mark the tile position with a label
195 | label = LABELS[labelIndex];
196 | for(y = 0; y < rect.height; y++) {
197 | for(x = 0; x < rect.width; x++) {
198 | rows[rect.y + y][rect.x + x] = label;
199 | }
200 | }
201 |
202 | // advance the label index
203 | labelIndex = (labelIndex + 1) % NUM_LABELS;
204 | }
205 | }
206 |
207 | // turn the rows into strings
208 | for (y = 0; y < this.numRows; y++) {
209 | rows[y] = rows[y].join('');
210 | }
211 |
212 | return rows;
213 | };
214 |
215 | // period used to designate a single 1x1 cell tile
216 | Tiles.Template.SINGLE_CELL = '.';
217 |
218 | })(jQuery);
219 |
--------------------------------------------------------------------------------
/src/Tile.js:
--------------------------------------------------------------------------------
1 |
2 | // single namespace export
3 | var Tiles = {};
4 |
5 | (function($) {
6 |
7 | var Tile = Tiles.Tile = function(tileId, element) {
8 |
9 | this.id = tileId;
10 |
11 | // position and dimensions of tile inside the parent panel
12 | this.top = 0;
13 | this.left = 0;
14 | this.width = 0;
15 | this.height = 0;
16 |
17 | // cache the tile container element
18 | this.$el = $(element || document.createElement('div'));
19 | };
20 |
21 | Tile.prototype.appendTo = function($parent, fadeIn, delay, duration) {
22 | this.$el
23 | .hide()
24 | .appendTo($parent);
25 |
26 | if (fadeIn) {
27 | this.$el.delay(delay).fadeIn(duration);
28 | }
29 | else {
30 | this.$el.show();
31 | }
32 | };
33 |
34 | Tile.prototype.remove = function(animate, duration) {
35 | if (animate) {
36 | this.$el.fadeOut({
37 | complete: function() {
38 | $(this).remove();
39 | }
40 | });
41 | }
42 | else {
43 | this.$el.remove();
44 | }
45 | };
46 |
47 | // updates the tile layout with optional animation
48 | Tile.prototype.resize = function(cellRect, pixelRect, animate, duration, onComplete) {
49 |
50 | // store the list of needed changes
51 | var cssChanges = {},
52 | changed = false;
53 |
54 | // update position and dimensions
55 | if (this.left !== pixelRect.x) {
56 | cssChanges.left = pixelRect.x;
57 | this.left = pixelRect.x;
58 | changed = true;
59 | }
60 | if (this.top !== pixelRect.y) {
61 | cssChanges.top = pixelRect.y;
62 | this.top = pixelRect.y;
63 | changed = true;
64 | }
65 | if (this.width !== pixelRect.width) {
66 | cssChanges.width = pixelRect.width;
67 | this.width = pixelRect.width;
68 | changed = true;
69 | }
70 | if (this.height !== pixelRect.height) {
71 | cssChanges.height = pixelRect.height;
72 | this.height = pixelRect.height;
73 | changed = true;
74 | }
75 |
76 | // Sometimes animation fails to set the css top and left correctly
77 | // in webkit. We'll validate upon completion of the animation and
78 | // set the properties again if they don't match the expected values.
79 | var tile = this,
80 | validateChangesAndComplete = function() {
81 | var el = tile.$el[0];
82 | if (tile.left !== el.offsetLeft) {
83 | //console.log ('mismatch left:' + tile.left + ' actual:' + el.offsetLeft + ' id:' + tile.id);
84 | tile.$el.css('left', tile.left);
85 | }
86 | if (tile.top !== el.offsetTop) {
87 | //console.log ('mismatch top:' + tile.top + ' actual:' + el.offsetTop + ' id:' + tile.id);
88 | tile.$el.css('top', tile.top);
89 | }
90 |
91 | if (onComplete) {
92 | onComplete();
93 | }
94 | };
95 |
96 |
97 | // make css changes with animation when requested
98 | if (animate && changed) {
99 |
100 | this.$el.animate(cssChanges, {
101 | duration: duration,
102 | easing: 'swing',
103 | complete: validateChangesAndComplete
104 | });
105 | }
106 | else {
107 |
108 | if (changed) {
109 | this.$el.css(cssChanges);
110 | }
111 |
112 | setTimeout(validateChangesAndComplete, duration);
113 | }
114 | };
115 |
116 | })(jQuery);
117 |
--------------------------------------------------------------------------------
/src/UniformTemplates.js:
--------------------------------------------------------------------------------
1 |
2 | // template provider which returns simple templates with 1x1 tiles
3 | Tiles.UniformTemplates = {
4 | get: function(numCols, targetTiles) {
5 | var numRows = Math.ceil(targetTiles / numCols),
6 | rects = [],
7 | x, y;
8 |
9 | // create the rects for 1x1 tiles
10 | for (y = 0; y < numRows; y++) {
11 | for (x = 0; x < numCols; x++) {
12 | rects.push(new Tiles.Rectangle(x, y, 1, 1));
13 | }
14 | }
15 |
16 | return new Tiles.Template(rects, numCols, numRows);
17 | }
18 | };
--------------------------------------------------------------------------------