README.md 22.6 KB
Newer Older
Pavel Reznikov's avatar
Pavel Reznikov committed
1
2
3
gridstack.js
============

Pavel Reznikov's avatar
Pavel Reznikov committed
4
gridstack.js is a jQuery plugin for widget layout. This is drag-and-drop multi-column grid. It allows you to build 
Pavel Reznikov's avatar
Pavel Reznikov committed
5
6
draggable responsive bootstrap v3 friendly layouts. It also works great with [knockout.js](http://knockoutjs.com) and
touch devices.
Pavel Reznikov's avatar
Pavel Reznikov committed
7

Pavel Reznikov's avatar
readme    
Pavel Reznikov committed
8
Inspired by [gridster.js](http://gridster.net). Built with love.
Pavel Reznikov's avatar
Pavel Reznikov committed
9

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Demo](#demo)
- [Usage](#usage)
  - [Requirements](#requirements)
  - [Basic usage](#basic-usage)
  - [Options](#options)
  - [Grid attributes](#grid-attributes)
  - [Item attributes](#item-attributes)
  - [Events](#events)
    - [onchange(items)](#onchangeitems)
    - [ondragstart(event, ui)](#ondragstartevent-ui)
    - [ondragstop(event, ui)](#ondragstopevent-ui)
    - [onresizestart(event, ui)](#onresizestartevent-ui)
    - [onresizestop(event, ui)](#onresizestopevent-ui)
  - [API](#api)
    - [add_widget(el, x, y, width, height, auto_position)](#add_widgetel-x-y-width-height-auto_position)
29
    - [batch_update()](#batch_update)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
30
31
32
    - [cell_height()](#cell_height)
    - [cell_height(val)](#cell_heightval)
    - [cell_width()](#cell_width)
33
    - [commit()](#commit)
Pavel Reznikov's avatar
Pavel Reznikov committed
34
35
    - [disable()](#disable)
    - [enable()](#enable)
Pavel Reznikov's avatar
Pavel Reznikov committed
36
    - [get_cell_from_pixel(position)](#get_cell_from_pixelposition)
Pavel Reznikov's avatar
Pavel Reznikov committed
37
    - [is_area_empty(x, y, width, height)](#is_area_emptyx-y-width-height)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
38
    - [locked(el, val)](#lockedel-val)
39
    - [remove_widget(el, detach_node)](#remove_widgetel-detach_node)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
40
41
42
43
44
    - [remove_all()](#remove_all)
    - [resize(el, width, height)](#resizeel-width-height)
    - [move(el, x, y)](#moveel-x-y)
    - [resizable(el, val)](#resizableel-val)
    - [movable(el, val)](#movableel-val)
Pavel Reznikov's avatar
Pavel Reznikov committed
45
    - [update(el, x, y, width, height)](#updateel-x-y-width-height)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
46
47
48
49
50
51
    - [will_it_fit(x, y, width, height, auto_position)](#will_it_fitx-y-width-height-auto_position)
  - [Utils](#utils)
    - [GridStackUI.Utils.sort(nodes, dir, width)](#gridstackuiutilssortnodes-dir-width)
  - [Touch devices support](#touch-devices-support)
  - [Use with knockout.js](#use-with-knockoutjs)
  - [Change grid width](#change-grid-width)
Pavel Reznikov's avatar
Pavel Reznikov committed
52
53
  - [Extra CSS](#extra-css)
    - [Different grid widths](#different-grid-widths)
Pavel Reznikov's avatar
Pavel Reznikov committed
54
  - [Save grid to array](#save-grid-to-array)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
55
  - [Load grid from array](#load-grid-from-array)
Pavel Reznikov's avatar
Pavel Reznikov committed
56
  - [Override resizable/draggable options](#override-resizabledraggable-options)
Pavel Reznikov's avatar
Pavel Reznikov committed
57
  - [IE8 support](#ie8-support)
Pavel Reznikov's avatar
Pavel Reznikov committed
58
  - [Nested grids](#nested-grids)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
59
60
61
62
63
64
65
66
67
68
69
- [Changes](#changes)
      - [v0.2.3 (development version)](#v023-development-version)
      - [v0.2.2 (2014-12-23)](#v022-2014-12-23)
      - [v0.2.1 (2014-12-09)](#v021-2014-12-09)
      - [v0.2.0 (2014-11-30)](#v020-2014-11-30)
      - [v0.1.0 (2014-11-18)](#v010-2014-11-18)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->


Pavel Reznikov's avatar
Pavel Reznikov committed
70
71
72
Demo
====

Pavel Reznikov's avatar
Pavel Reznikov committed
73
Please visit http://troolee.github.io/gridstack.js/ for demo.
Pavel Reznikov's avatar
Pavel Reznikov committed
74
75
76
77


Usage
=====
Pavel Reznikov's avatar
readme    
Pavel Reznikov committed
78

Pavel Reznikov's avatar
Pavel Reznikov committed
79
80
## Requirements

Pavel Reznikov's avatar
Pavel Reznikov committed
81
* [lodash.js](https://lodash.com) (>= 3.5.0)
Pavel Reznikov's avatar
Pavel Reznikov committed
82
* [jQuery](http://jquery.com) (>= 1.11.0) 
Pavel Reznikov's avatar
Pavel Reznikov committed
83
84
85
* [jQuery UI](http://jqueryui.com) (>= 1.11.0). Minimum required components: Core, Widget, Mouse, Draggable, Resizable
* (Optional) [knockout.js](http://knockoutjs.com) (>= 3.2.0)
* (Optional) [jquery-ui-touch-punch](https://github.com/furf/jquery-ui-touch-punch) for touch-based devices support
Pavel Reznikov's avatar
license    
Pavel Reznikov committed
86

Pavel Reznikov's avatar
Pavel Reznikov committed
87
88
Note: You can still use [underscore.js](http://underscorejs.org) (>= 1.7.0) instead of lodash.js

Pavel Reznikov's avatar
Pavel Reznikov committed
89
90
91
## Basic usage

```html
92
93
94
95
96
<div class="grid-stack">
    <div class="grid-stack-item" 
        data-gs-x="0" data-gs-y="0" 
        data-gs-width="4" data-gs-height="2">
            <div class="grid-stack-item-content"></div>
Pavel Reznikov's avatar
Pavel Reznikov committed
97
    </div>
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    <div class="grid-stack-item" 
        data-gs-x="4" data-gs-y="0" 
        data-gs-width="4" data-gs-height="4">
            <div class="grid-stack-item-content"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var options = {
        cell_height: 80,
        vertical_margin: 10
    };
    $('.grid-stack').gridstack(options);
});
</script>
Pavel Reznikov's avatar
Pavel Reznikov committed
114
115
```

Pavel Reznikov's avatar
Pavel Reznikov committed
116
117
## Options

Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
118
- `always_show_resize_handle` - if `true` the resizing handles are shown even if the user is not hovering over the widget 
Pavel Reznikov's avatar
Pavel Reznikov committed
119
    (default: `false`) 
Pavel Reznikov's avatar
Pavel Reznikov committed
120
- `animate` - turns animation on (default: `false`)
Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
121
- `auto` - if `false` gridstack will not initialize existing items (default: `true`)
Pavel Reznikov's avatar
Pavel Reznikov committed
122
- `cell_height` - one cell height (default: `60`)
babybeasimple's avatar
babybeasimple committed
123
- `draggable` - allows to override jQuery UI draggable options. (default: `{handle: '.grid-stack-item-content', scroll: true, appendTo: 'body'}`) 
Pavel Reznikov's avatar
Pavel Reznikov committed
124
- `handle` - draggable handle selector (default: `'.grid-stack-item-content'`)
Pavel Reznikov's avatar
Pavel Reznikov committed
125
- `height` - maximum rows amount. Default is `0` which means no maximum rows
Pavel Reznikov's avatar
Pavel Reznikov committed
126
- `float` - enable floating widgets (default: `false`) See [example](http://troolee.github.io/gridstack.js/demo/float.html)
Pavel Reznikov's avatar
Pavel Reznikov committed
127
- `item_class` - widget class (default: `'grid-stack-item'`)
Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
128
- `min_width` - minimal width. If window width is less, grid will be shown in one-column mode (default: `768`)
Pavel Reznikov's avatar
Pavel Reznikov committed
129
- `placeholder_class` - class for placeholder (default: `'grid-stack-placeholder'`)
babybeasimple's avatar
babybeasimple committed
130
- `resizable` - allows to override jQuery UI resizable options. (default: `{autoHide: true, handles: 'se'}`)
Pavel Reznikov's avatar
Pavel Reznikov committed
131
132
- `vertical_margin` - vertical gap size (default: `20`)
- `width` - amount of columns (default: `12`)
Pavel Reznikov's avatar
Pavel Reznikov committed
133

Pavel Reznikov's avatar
Pavel Reznikov committed
134
135
## Grid attributes

Pavel Reznikov's avatar
Pavel Reznikov committed
136
137
138
- `data-gs-animate` - turns animation on 
- `data-gs-width` - amount of columns
- `data-gs-height` - maximum rows amount. Default is `0` which means no maximum rows.
Pavel Reznikov's avatar
Pavel Reznikov committed
139

Pavel Reznikov's avatar
Pavel Reznikov committed
140
141
142
143
144
145
## Item attributes

- `data-gs-x`, `data-gs-y` - element position
- `data-gs-width`, `data-gs-height` - element size
- `data-gs-max-width`, `data-gs-min-width`, `data-gs-max-height`, `data-gs-min-height` - element constraints
- `data-gs-no-resize` - disable element resizing
Pavel Reznikov's avatar
Pavel Reznikov committed
146
- `data-gs-no-move` - disable element moving 
Pavel Reznikov's avatar
Pavel Reznikov committed
147
148
- `data-gs-auto-position` - tells to ignore `data-gs-x` and `data-gs-y` attributes and to place element to the first 
    available position
Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
149
150
- `data-gs-locked` - the widget will be locked. It means another widget wouldn't be able to move it during dragging or resizing.
The widget can still be dragged or resized. You need to add `data-gs-no-resize` and `data-gs-no-move` attributes
Pavel Reznikov's avatar
Pavel Reznikov committed
151
to completely lock the widget.
Pavel Reznikov's avatar
Pavel Reznikov committed
152
153
154
    
## Events

Pavel Reznikov's avatar
Pavel Reznikov committed
155
### onchange(items)
Pavel Reznikov's avatar
Pavel Reznikov committed
156
157
158
159

Occurs when widgets change their position/size

```javascript
160
161
162
var serialize_widget_map = function (items) {
    console.log(items);
};
Pavel Reznikov's avatar
samples    
Pavel Reznikov committed
163

164
165
166
$('.grid-stack').on('change', function (e, items) {
    serialize_widget_map(items);
});
Pavel Reznikov's avatar
Pavel Reznikov committed
167
168
```

Pavel Reznikov's avatar
Pavel Reznikov committed
169
170
171
### ondragstart(event, ui)

```javascript
172
173
174
175
$('.grid-stack').on('dragstart', function (event, ui) {
    var grid = this;
    var element = event.target;
});
Pavel Reznikov's avatar
Pavel Reznikov committed
176
177
178
179
180
```

### ondragstop(event, ui)

```javascript
181
182
183
184
$('.grid-stack').on('dragstop', function (event, ui) {
    var grid = this;
    var element = event.target;
});
Pavel Reznikov's avatar
Pavel Reznikov committed
185
186
187
188
189
```

### onresizestart(event, ui)

```javascript
190
191
192
193
$('.grid-stack').on('resizestart', function (event, ui) {
    var grid = this;
    var element = event.target;
});
Pavel Reznikov's avatar
Pavel Reznikov committed
194
195
196
197
198
```

### onresizestop(event, ui)

```javascript
199
200
201
202
$('.grid-stack').on('resizestop', function (event, ui) {
    var grid = this;
    var element = event.target;
});
Pavel Reznikov's avatar
Pavel Reznikov committed
203
204
205
```


Pavel Reznikov's avatar
Pavel Reznikov committed
206
207
208
209
## API

### add_widget(el, x, y, width, height, auto_position)

210
Creates new widget and returns it.
Pavel Reznikov's avatar
Pavel Reznikov committed
211
212
213
214
215
216
217
218

Parameters:

- `el` - widget to add
- `x`, `y`, `width`, `height` - widget position/dimensions (Optional)
- `auto_position` - if `true` then `x`, `y` parameters will be ignored and widget will be places on the first available
position

Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
219
220
Widget will be always placed even if result height is more than actual grid height. You need to use `will_it_fit` method
before calling `add_widget` for additional check.
Pavel Reznikov's avatar
Pavel Reznikov committed
221

Pavel Reznikov's avatar
Pavel Reznikov committed
222
```javascript
Pavel Reznikov's avatar
Pavel Reznikov committed
223
$('.grid-stack').gridstack();
Pavel Reznikov's avatar
Pavel Reznikov committed
224

Pavel Reznikov's avatar
Pavel Reznikov committed
225
var grid = $('.grid-stack').data('gridstack');
Pavel Reznikov's avatar
Pavel Reznikov committed
226
227
228
grid.add_widget(el, 0, 0, 3, 2, true);
```

229
230
231
232
### batch_update()

Initailizes batch updates. You will see no changes until `commit` method is called. 

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
### cell_height()

Gets current cell height.

### cell_height(val)

Update current cell height. This method rebuilds an internal CSS stylesheet. Note: You can expect performance issues if
call this method too often.

```javascript
grid.cell_height(grid.cell_width() * 1.2);
```

### cell_width()

Gets current cell width.

250
251
252
253
### commit()

Finishes batch updates. Updates DOM nodes. You must call it after `batch_update`.

Pavel Reznikov's avatar
Pavel Reznikov committed
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
### disable()

Disables widgets moving/resizing. This is a shortcut for:

```javascript
grid.movable('.grid-stack-item', false);
grid.resizable('.grid-stack-item', false);
```

### enable()

Enables widgets moving/resizing. This is a shortcut for:

```javascript
grid.movable('.grid-stack-item', true);
grid.resizable('.grid-stack-item', true);
```

272
273
274
275
276
277
278
279
280
281
### get_cell_from_pixel(position)

Get the position of the cell under a pixel on screen.

Parameters :

- `position` - the position of the pixel to resolve in absolute coordinates, as an object with `top` and `left`properties

Returns an object with properties `x` and `y` i.e. the column and row in the grid.

Pavel Reznikov's avatar
Pavel Reznikov committed
282
283
284
285
### is_area_empty(x, y, width, height)

Checks if specified area is empty.

Pavel Reznikov's avatar
Pavel Reznikov committed
286
287
288
289
290
291
292
### locked(el, val)

Locks/unlocks widget.

- `el` - widget to modify.
- `val` - if `true` widget will be locked. 

293
### remove_widget(el, detach_node)
Pavel Reznikov's avatar
Pavel Reznikov committed
294
295
296
297
298

Removes widget from the grid.

Parameters:

299
300
- `el` - widget to remove.
- `detach_node` - if `false` DOM node won't be removed from the tree (Optional. Default `true`).
Pavel Reznikov's avatar
Pavel Reznikov committed
301

302
303
304
305
### remove_all()

Removes all widgets from the grid.

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
306
### resize(el, width, height)
307
308
309
310
311
312

Changes widget size

Parameters:

- `el` - widget to resize
Pavel Reznikov's avatar
Pavel Reznikov committed
313
- `width`, `height` - new dimensions. If value is `null` or `undefined` it will be ignored.
314

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
315
### move(el, x, y)
316
317
318
319
320
321

Changes widget position

Parameters:

- `el` - widget to move
Pavel Reznikov's avatar
Pavel Reznikov committed
322
- `x`, `y` - new position. If value is `null` or `undefined` it will be ignored.
323

324
325
326
327
328
329
330
331
332
333
334
335
### resizable(el, val)

Enables/Disables resizing.

- `el` - widget to modify
- `val` - if `true` widget will be resizable. 

### movable(el, val)

Enables/Disables moving.

- `el` - widget to modify
336
- `val` - if `true` widget will be draggable.
Pavel Reznikov's avatar
Pavel Reznikov committed
337

Pavel Reznikov's avatar
Pavel Reznikov committed
338
339
340
341
342
343
344
345
346
347
### update(el, x, y, width, height)

Parameters:

- `el` - widget to move
- `x`, `y` - new position. If value is `null` or `undefined` it will be ignored.
- `width`, `height` - new dimensions. If value is `null` or `undefined` it will be ignored.

Updates widget position/size.

Pavel Reznikov's avatar
Pavel Reznikov committed
348
349
350
351
352
353
354
### will_it_fit(x, y, width, height, auto_position)

Returns `true` if the `height` of the grid will be less the vertical constraint. Always returns `true` if grid doesn't
have `height` constraint.

```javascript
if (grid.will_it_fit(new_node.x, new_node.y, new_node.width, new_node.height, true)) {
Jean-Frédéric Faust's avatar
Jean-Frédéric Faust committed
355
    grid.add_widget(new_node.el, new_node.x, new_node.y, new_node.width, new_node.height, true);
Pavel Reznikov's avatar
Pavel Reznikov committed
356
357
}
else {
Pavel Reznikov's avatar
typo    
Pavel Reznikov committed
358
    alert('Not enough free space to place the widget');
Pavel Reznikov's avatar
Pavel Reznikov committed
359
360
}
```
361
 
Pavel Reznikov's avatar
Pavel Reznikov committed
362

363
364
## Utils

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
365
### GridStackUI.Utils.sort(nodes, dir, width)
366
367
368
369

Sorts array of nodes

- `nodes` - array to sort
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
370
371
- `dir` - `1` for asc, `-1` for desc (optional)
- `width` - width of the grid. If `undefined` the width will be calculated automatically (optional).
372

Pavel Reznikov's avatar
Pavel Reznikov committed
373
374
## Touch devices support

Pavel Reznikov's avatar
Pavel Reznikov committed
375
Please use [jQuery UI Touch Punch](https://github.com/furf/jquery-ui-touch-punch) to make jQuery UI Draggable/Resizable
Pavel Reznikov's avatar
Pavel Reznikov committed
376
377
working on touch-based devices.

Pavel Reznikov's avatar
Pavel Reznikov committed
378
```html
Pavel Reznikov's avatar
Pavel Reznikov committed
379
<script src="lodash.min.js"></script>
Pavel Reznikov's avatar
Pavel Reznikov committed
380
<script src="jquery.min.js"></script>
Pavel Reznikov's avatar
Pavel Reznikov committed
381
382
383
384
385
386
<script src="jquery-ui.min.js"></script>
<script src="jquery.ui.touch-punch.min.js"></script>

<script src="gridstack.js"></script>
```

Pavel Reznikov's avatar
Pavel Reznikov committed
387
388
389
390
391
392
393
394
395
396
397
Also `always_show_resize_handle` option may be useful:

```javascript
$(function () {
    var options = {
        always_show_resize_handle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
    };
    $('.grid-stack').gridstack(options);
});
```

Pavel Reznikov's avatar
Pavel Reznikov committed
398
399
400
401
402
## Use with knockout.js

```javascript
ko.components.register('dashboard-grid', {
    viewModel: {
Pavel Reznikov's avatar
Pavel Reznikov committed
403
404
        createViewModel: function (controller, componentInfo) {
            var ViewModel = function (controller, componentInfo) {
Pavel Reznikov's avatar
Pavel Reznikov committed
405
406
                var grid = null;

Pavel Reznikov's avatar
Pavel Reznikov committed
407
                this.widgets = controller.widgets;
Pavel Reznikov's avatar
Pavel Reznikov committed
408
409

                this.afterAddWidget = function (items) {
Pavel Reznikov's avatar
Pavel Reznikov committed
410
411
412
413
414
415
416
417
418
419
420
                    if (grid == null) {
                        grid = $(componentInfo.element).find('.grid-stack').gridstack({
                            auto: false
                        }).data('gridstack');
                    }

                    var item = _.find(items, function (i) { return i.nodeType == 1 });
                    grid.add_widget(item);
                    ko.utils.domNodeDisposal.addDisposeCallback(item, function () {
                        grid.remove_widget(item);
                    });
Pavel Reznikov's avatar
Pavel Reznikov committed
421
422
423
                };
            };

Pavel Reznikov's avatar
Pavel Reznikov committed
424
            return new ViewModel(controller, componentInfo);
Pavel Reznikov's avatar
Pavel Reznikov committed
425
426
        }
    },
Pavel Reznikov's avatar
Pavel Reznikov committed
427
428
429
    template:
        [
            '<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">',
Pavel Reznikov's avatar
Pavel Reznikov committed
430
431
            '   <div class="grid-stack-item" data-bind="attr: {\'data-gs-x\': $data.x, \'data-gs-y\': $data.y, \'data-gs-width\': $data.width, \'data-gs-height\': $data.height, \'data-gs-auto-position\': $data.auto_position}">',
            '       <div class="grid-stack-item-content">...</div>',
Pavel Reznikov's avatar
Pavel Reznikov committed
432
            '   </div>',
Pavel Reznikov's avatar
Pavel Reznikov committed
433
434
            '</div> '
        ].join('')
Pavel Reznikov's avatar
Pavel Reznikov committed
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
});

$(function () {
    var Controller = function (widgets) {
        this.widgets = ko.observableArray(widgets);
    };

    var widgets = [
        {x: 0, y: 0, width: 2, height: 2},
        {x: 2, y: 0, width: 4, height: 2},
        {x: 6, y: 0, width: 2, height: 4},
        {x: 1, y: 2, width: 4, height: 2}
    ];

    ko.applyBindings(new Controller(widgets));
Pavel Reznikov's avatar
Pavel Reznikov committed
450
451
452
453
454
455
456
457
458
});
```

and HTML:

```html
<div data-bind="component: {name: 'dashboard-grid', params: $data}"></div>
```

Pavel Reznikov's avatar
Pavel Reznikov committed
459
See examples: [example 1](http://troolee.github.io/gridstack.js/demo/knockout.html), [example 2](http://troolee.github.io/gridstack.js/demo/knockout2.html).
Pavel Reznikov's avatar
Pavel Reznikov committed
460

Pavel Reznikov's avatar
Pavel Reznikov committed
461
462
463
**Notes:** It's very important to exclude training spaces after widget template:

```
Pavel Reznikov's avatar
Pavel Reznikov committed
464
465
466
467
468
469
470
471
template:
    [
        '<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">',
        '   <div class="grid-stack-item" data-bind="attr: {\'data-gs-x\': $data.x, \'data-gs-y\': $data.y, \'data-gs-width\': $data.width, \'data-gs-height\': $data.height, \'data-gs-auto-position\': $data.auto_position}">',
        '       ....',
        '   </div>', // <-- NO SPACE **AFTER** </div>
        '</div> '    // <-- NO SPACE **BEFORE** </div>
    ].join('')       // <-- JOIN WITH **EMPTY** STRING 
Pavel Reznikov's avatar
Pavel Reznikov committed
472
473
474
475
```

Otherwise `addDisposeCallback` won't work.

Pavel Reznikov's avatar
Pavel Reznikov committed
476

477
478
## Change grid width

479
480
To change grid width (columns count), to addition to `width` option, CSS rules 
for `.grid-stack-item[data-gs-width="X"]` and  `.grid-stack-item[data-gs-x="X"]` have to be changed accordingly. 
481

482
For instance for 3-column grid you need to rewrite CSS to be:
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505

```css
.grid-stack-item[data-gs-width="3"]  { width: 100% }
.grid-stack-item[data-gs-width="2"]  { width: 66.66666667% }
.grid-stack-item[data-gs-width="1"]  { width: 33.33333333% }

.grid-stack-item[data-gs-x="2"]  { left: 66.66666667% }
.grid-stack-item[data-gs-x="1"]  { left: 33.33333333% }
```

For 4-column grid it should be:

```css
.grid-stack-item[data-gs-width="4"]  { width: 100% }
.grid-stack-item[data-gs-width="3"]  { width: 75% }
.grid-stack-item[data-gs-width="2"]  { width: 50% }
.grid-stack-item[data-gs-width="1"]  { width: 25% }

.grid-stack-item[data-gs-x="3"]  { left: 75% }
.grid-stack-item[data-gs-x="2"]  { left: 50% }
.grid-stack-item[data-gs-x="1"]  { left: 25% }
```

506
507
and so on.

Pavel Reznikov's avatar
Pavel Reznikov committed
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
Here is a SASS code snipped which can make life easier (Thanks to @ascendantofrain, [#81](https://github.com/troolee/gridstack.js/issues/81)):

```sass
.grid-stack-item {

    $gridstack-columns: 12;

    @for $i from 1 through $gridstack-columns {
        &[data-gs-width='#{$i}'] { width: (100% / $gridstack-columns) * $i; }
        &[data-gs-x='#{$i}'] { left: (100% / $gridstack-columns) * $i; }
        &.grid-stack-item[data-gs-min-width='#{$i}'] { min-width: (100% / $gridstack-columns) * $i; }
        &.grid-stack-item[data-gs-max-width='#{$i}'] { max-width: (100% / $gridstack-columns) * $i; }
    }
}
```

Pavel Reznikov's avatar
Pavel Reznikov committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
Or you can include `gridstack-extra.css`. See below for more details.

## Extra CSS

There are few extra CSS batteries in `gridstack-extra.css` (`gridstack-extra.min.css`).
 
### Different grid widths

You can use other than 12 grid width:

```html
<div class="grid-stack grid-stack-N">...</div>
```
```javascript
$('.grid-stack').gridstack({width: N});
```

See example: [2 grids demo](http://troolee.github.io/gridstack.js/demo/two.html)

Pavel Reznikov's avatar
Pavel Reznikov committed
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
## Save grid to array

Because gridstack doesn't track any kind of user-defined widget id there is no reason to make serialization to be part
of gridstack API. To serialize grid you can simply do something like this (let's say you store widget id inside `data-custom-id` 
attribute):

```javascript
var res = _.map($('.grid-stack .grid-stack-item:visible'), function (el) {
    el = $(el);
    var node = el.data('_gridstack_node');
    return {
        id: el.attr('data-custom-id'),
        x: node.x,
        y: node.y,
        width: node.width,
        height: node.height
    };
});
alert(JSON.stringify(res));
```

Pavel Reznikov's avatar
Pavel Reznikov committed
564
565
See example: [Serialization demo](http://troolee.github.io/gridstack.js/demo/serialization.html)

Pavel Reznikov's avatar
Pavel Reznikov committed
566
567
You can also use `onchange` event if you need to save only changed widgets right away they have been changed. 

Pavel Reznikov's avatar
Pavel Reznikov committed
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
## Load grid from array

```javascript
var serialization = [
    {x: 0, y: 0, width: 2, height: 2},
    {x: 3, y: 1, width: 1, height: 2},
    {x: 4, y: 1, width: 1, height: 1},
    {x: 2, y: 3, width: 3, height: 1},
    {x: 1, y: 4, width: 1, height: 1},
    {x: 1, y: 3, width: 1, height: 1},
    {x: 2, y: 4, width: 1, height: 1},
    {x: 2, y: 5, width: 1, height: 1}
];

serialization = GridStackUI.Utils.sort(serialization);

var grid = $('.grid-stack').data('gridstack');
grid.remove_all();

_.each(serialization, function (node) {
    grid.add_widget($('<div><div class="grid-stack-item-content" /><div/>'), 
        node.x, node.y, node.width, node.height);
});
```

Pavel Reznikov's avatar
Pavel Reznikov committed
593
594
See example: [Serialization demo](http://troolee.github.io/gridstack.js/demo/serialization.html)

Pavel Reznikov's avatar
Pavel Reznikov committed
595
596
If you're using knockout there is no need for such method at all.

Pavel Reznikov's avatar
Pavel Reznikov committed
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
## Override resizable/draggable options

You can override default `resizable`/`draggable` options. For instance to enable other then bottom right resizing handle
you can init gridsack like:

```javascript
$('.grid-stack').gridstack({
    resizable: {
        handles: 'e, se, s, sw, w'
    }
});
```

Note: It's not recommended to enable `nw`, `n`, `ne` resizing handles. Their behaviour may be unexpected.

Pavel Reznikov's avatar
Pavel Reznikov committed
612
613
614
## IE8 support

Support of IE8 is quite limited and is not a goal at this time. As far as IE8 doesn't support DOM Level 2 I cannot manipulate with
Pavel Reznikov's avatar
typo    
Pavel Reznikov committed
615
CSS stylesheet dynamically. As a workaround you can do the following:
Pavel Reznikov's avatar
Pavel Reznikov committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656

- Create `gridstack-ie8.css` for your configuration (sample for grid with cell height of 60px can be found [here](https://gist.github.com/troolee/6edfea5857f4cd73e6f1)).
- Include this CSS:

```html
<!--[if lt IE 9]>
<link rel="stylesheet" href="gridstack-ie8.css"/>
<![endif]-->
```

- You can use this python script to generate such kind of CSS:

```python
#!/usr/bin/env python

height = 60
margin = 20
N = 100

print '.grid-stack > .grid-stack-item { min-height: %(height)spx }' % {'height': height}

for i in range(N):
	h = height * (i + 1) + margin * i
	print '.grid-stack > .grid-stack-item[data-gs-height="%(index)s"] { height: %(height)spx }' % {'index': i + 1, 'height': h}

for i in range(N):
	h = height * (i + 1) + margin * i
	print '.grid-stack > .grid-stack-item[data-gs-min-height="%(index)s"] { min-height: %(height)spx }' % {'index': i + 1, 'height': h}

for i in range(N):
	h = height * (i + 1) + margin * i
	print '.grid-stack > .grid-stack-item[data-gs-max-height="%(index)s"] { max-height: %(height)spx }' % {'index': i + 1, 'height': h}

for i in range(N):
	h = height * i + margin * i
	print '.grid-stack > .grid-stack-item[data-gs-y="%(index)s"] { top: %(height)spx }' % {'index': i , 'height': h}
```

There are at least two more issues with gridstack in IE8 with jQueryUI resizable (it seems it doesn't work) and 
droppable. If you have any suggestions about support of IE8 you are welcome here: https://github.com/troolee/gridstack.js/issues/76 

Pavel Reznikov's avatar
Pavel Reznikov committed
657

Pavel Reznikov's avatar
Pavel Reznikov committed
658
659
660
661
662
663
664
## Nested grids

Gridstack may be nested. All nested grids have an additional class `grid-stack-nested` which is assigned automatically 
during initialization. 
See example: [Nested grid demo](http://troolee.github.io/gridstack.js/demo/nested.html)


665
666
667
Changes
=======

Pavel Reznikov's avatar
v0.2.3    
Pavel Reznikov committed
668
#### v0.2.3 (2015-06-23)
Pavel Reznikov's avatar
Pavel Reznikov committed
669

Pavel Reznikov's avatar
Pavel Reznikov committed
670
- gridstack-extra.css
Pavel Reznikov's avatar
Pavel Reznikov committed
671
- add support of lodash.js
Pavel Reznikov's avatar
Pavel Reznikov committed
672
- add `is_area_empty` method
Pavel Reznikov's avatar
Pavel Reznikov committed
673
- nested grids
674
- add `batch_update`/`commit` methods
Pavel Reznikov's avatar
Pavel Reznikov committed
675
- add `update` method
Pavel Reznikov's avatar
Pavel Reznikov committed
676
- allow to override `resizable`/`draggable` options
Pavel Reznikov's avatar
Pavel Reznikov committed
677
- add `disable`/`enable` methods
Pavel Reznikov's avatar
Pavel Reznikov committed
678
- add `get_cell_from_pixel` (thanks to @juchi)
Pavel Reznikov's avatar
Pavel Reznikov committed
679
- AMD support
Pavel Reznikov's avatar
changes    
Pavel Reznikov committed
680
- fix nodes sorting
Pavel Reznikov's avatar
Pavel Reznikov committed
681
682
- improved touch devices support
- add `always_show_resize_handle` option
Pavel Reznikov's avatar
Pavel Reznikov committed
683
- minor fixes and improvements
Pavel Reznikov's avatar
Pavel Reznikov committed
684

Pavel Reznikov's avatar
v0.2.2    
Pavel Reznikov committed
685
#### v0.2.2 (2014-12-23)
Pavel Reznikov's avatar
Pavel Reznikov committed
686

Pavel Reznikov's avatar
Pavel Reznikov committed
687
- fix grid initialization
688
- add `cell_height`/`cell_width` API methods
Pavel Reznikov's avatar
Pavel Reznikov committed
689
690
- fix boolean attributes (issue #31)

Pavel Reznikov's avatar
Pavel Reznikov committed
691
#### v0.2.1 (2014-12-09)
Pavel Reznikov's avatar
Pavel Reznikov committed
692

Pavel Reznikov's avatar
Pavel Reznikov committed
693
- add widgets locking (issue #19)
Pavel Reznikov's avatar
Pavel Reznikov committed
694
695
- add `will_it_fit` API method
- fix auto-positioning (issue #20)
Pavel Reznikov's avatar
Pavel Reznikov committed
696
- add animation (thanks to @ishields)
Pavel Reznikov's avatar
Pavel Reznikov committed
697
- fix `y` coordinate calculation when dragging (issue #18)
Pavel Reznikov's avatar
Pavel Reznikov committed
698
- fix `remove_widget` (issue #16)
Pavel Reznikov's avatar
Pavel Reznikov committed
699
- minor fixes
Pavel Reznikov's avatar
Pavel Reznikov committed
700
701


Pavel Reznikov's avatar
Pavel Reznikov committed
702
#### v0.2.0 (2014-11-30)
Pavel Reznikov's avatar
Pavel Reznikov committed
703

Pavel Reznikov's avatar
Pavel Reznikov committed
704
- add `height` option
Pavel Reznikov's avatar
Pavel Reznikov committed
705
- auto-generate css rules (widgets `height` and `top`)
Pavel Reznikov's avatar
Pavel Reznikov committed
706
707
- add `GridStackUI.Utils.sort` utility function
- add `remove_all` API method
708
- add `resize` and `move` API methods 
709
710
- add `resizable` and `movable` API methods
- add `data-gs-no-move` attribute
Pavel Reznikov's avatar
Pavel Reznikov committed
711
712
- add `float` option
- fix default css rule for inner content
Pavel Reznikov's avatar
Pavel Reznikov committed
713
- minor fixes
Pavel Reznikov's avatar
Pavel Reznikov committed
714

715
716
#### v0.1.0 (2014-11-18)

Pavel Reznikov's avatar
Pavel Reznikov committed
717
718
Very first version.

719

Pavel Reznikov's avatar
license    
Pavel Reznikov committed
720
721
722
723
724
License
=======

The MIT License (MIT)

Pavel Reznikov's avatar
Pavel Reznikov committed
725
Copyright (c) 2014-2015 Pavel Reznikov
Pavel Reznikov's avatar
license    
Pavel Reznikov committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.