README.md 21.9 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
  - [Save grid to array](#save-grid-to-array)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
53
  - [Load grid from array](#load-grid-from-array)
Pavel Reznikov's avatar
Pavel Reznikov committed
54
  - [Override resizable/draggable options](#override-resizabledraggable-options)
Pavel Reznikov's avatar
Pavel Reznikov committed
55
  - [IE8 support](#ie8-support)
Pavel Reznikov's avatar
Pavel Reznikov committed
56
  - [Nested grids](#nested-grids)
Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
57
58
59
60
61
62
63
64
65
66
67
- [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
68
69
70
Demo
====

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


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

Pavel Reznikov's avatar
Pavel Reznikov committed
77
78
## Requirements

Pavel Reznikov's avatar
Pavel Reznikov committed
79
* [underscore.js](http://underscorejs.org) (>= 1.7.0)
Pavel Reznikov's avatar
Pavel Reznikov committed
80
* [jQuery](http://jquery.com) (>= 1.11.0) 
Pavel Reznikov's avatar
Pavel Reznikov committed
81
82
83
* [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
84

Pavel Reznikov's avatar
Pavel Reznikov committed
85
86
87
## Basic usage

```html
88
89
90
91
92
<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
93
    </div>
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    <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
110
111
```

Pavel Reznikov's avatar
Pavel Reznikov committed
112
113
## Options

Pavel Reznikov's avatar
Pavel Reznikov committed
114
115
- `always_show_resize_handle` - if `true` the resizing handles are shown even the user is not hovering over the widget 
    (default: `false`) 
Pavel Reznikov's avatar
Pavel Reznikov committed
116
117
118
- `animate` - turns animation on (default: `false`)
- `auto` - if `false` it tells to do not initialize existing items (default: `true`)
- `cell_height` - one cell height (default: `60`)
Pavel Reznikov's avatar
Pavel Reznikov committed
119
- `draggable` - allows to owerride jQuery UI draggable options. (default: `{handle: '.grid-stack-item-content', scroll: true, appendTo: 'body'}`) 
Pavel Reznikov's avatar
Pavel Reznikov committed
120
- `handle` - draggable handle selector (default: `'.grid-stack-item-content'`)
Pavel Reznikov's avatar
Pavel Reznikov committed
121
- `height` - maximum rows amount. Default is `0` which means no maximum rows
Pavel Reznikov's avatar
Pavel Reznikov committed
122
- `float` - enable floating widgets (default: `false`)
Pavel Reznikov's avatar
Pavel Reznikov committed
123
- `item_class` - widget class (default: `'grid-stack-item'`)
Pavel Reznikov's avatar
Pavel Reznikov committed
124
- `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
125
- `placeholder_class` - class for placeholder (default: `'grid-stack-placeholder'`)
Pavel Reznikov's avatar
Pavel Reznikov committed
126
- `resizable` - allows to owerride jQuery UI resizable options. (default: `{autoHide: true, handles: 'se'}`)
Pavel Reznikov's avatar
Pavel Reznikov committed
127
128
- `vertical_margin` - vertical gap size (default: `20`)
- `width` - amount of columns (default: `12`)
Pavel Reznikov's avatar
Pavel Reznikov committed
129

Pavel Reznikov's avatar
Pavel Reznikov committed
130
131
## Grid attributes

Pavel Reznikov's avatar
Pavel Reznikov committed
132
133
134
- `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
135

Pavel Reznikov's avatar
Pavel Reznikov committed
136
137
138
139
140
141
## 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
142
- `data-gs-no-move` - disable element moving 
Pavel Reznikov's avatar
Pavel Reznikov committed
143
144
- `data-gs-auto-position` - tells to ignore `data-gs-x` and `data-gs-y` attributes and to place element to the first 
    available position
Pavel Reznikov's avatar
Pavel Reznikov committed
145
146
147
- `data-gs-locked` - the widget will be locked. It means another widgets couldn't move it during dragging or resizing.
The widget is still can be dragged or resized. You need to add `data-gs-no-resize` and `data-gs-no-move` attributes
to completely lock the widget.
Pavel Reznikov's avatar
Pavel Reznikov committed
148
149
150
    
## Events

Pavel Reznikov's avatar
Pavel Reznikov committed
151
### onchange(items)
Pavel Reznikov's avatar
Pavel Reznikov committed
152
153
154
155

Occurs when widgets change their position/size

```javascript
156
157
158
var serialize_widget_map = function (items) {
    console.log(items);
};
Pavel Reznikov's avatar
samples    
Pavel Reznikov committed
159

160
161
162
$('.grid-stack').on('change', function (e, items) {
    serialize_widget_map(items);
});
Pavel Reznikov's avatar
Pavel Reznikov committed
163
164
```

Pavel Reznikov's avatar
Pavel Reznikov committed
165
166
167
### ondragstart(event, ui)

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

### ondragstop(event, ui)

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

### onresizestart(event, ui)

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

### onresizestop(event, ui)

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


Pavel Reznikov's avatar
Pavel Reznikov committed
202
203
204
205
## API

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

206
Creates new widget and returns it.
Pavel Reznikov's avatar
Pavel Reznikov committed
207
208
209
210
211
212
213
214

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

Pavel Reznikov's avatar
Pavel Reznikov committed
215
216
217
Widget will be always placed even if result height will be more then grid height. You need to use `will_it_fit` method
before call `add_widget` for additional check.

Pavel Reznikov's avatar
Pavel Reznikov committed
218
```javascript
Pavel Reznikov's avatar
Pavel Reznikov committed
219
$('.grid-stack').gridstack();
Pavel Reznikov's avatar
Pavel Reznikov committed
220

Pavel Reznikov's avatar
Pavel Reznikov committed
221
var grid = $('.grid-stack').data('gridstack');
Pavel Reznikov's avatar
Pavel Reznikov committed
222
223
224
grid.add_widget(el, 0, 0, 3, 2, true);
```

225
226
227
228
### batch_update()

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

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
### 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.

246
247
248
249
### commit()

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

Pavel Reznikov's avatar
Pavel Reznikov committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
### 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);
```

268
269
270
271
272
273
274
275
276
277
### 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
278
279
280
281
### is_area_empty(x, y, width, height)

Checks if specified area is empty.

Pavel Reznikov's avatar
Pavel Reznikov committed
282
283
284
285
286
287
288
### locked(el, val)

Locks/unlocks widget.

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

289
### remove_widget(el, detach_node)
Pavel Reznikov's avatar
Pavel Reznikov committed
290
291
292
293
294

Removes widget from the grid.

Parameters:

295
296
- `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
297

298
299
300
301
### remove_all()

Removes all widgets from the grid.

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
302
### resize(el, width, height)
303
304
305
306
307
308

Changes widget size

Parameters:

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

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
311
### move(el, x, y)
312
313
314
315
316
317

Changes widget position

Parameters:

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

320
321
322
323
324
325
326
327
328
329
330
331
### 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
332
- `val` - if `true` widget will be draggable.
Pavel Reznikov's avatar
Pavel Reznikov committed
333

Pavel Reznikov's avatar
Pavel Reznikov committed
334
335
336
337
338
339
340
341
342
343
### 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
344
345
346
347
348
349
350
351
352
353
### 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)) {
    grid.add_widget(new_node.x, new_node.y, new_node.width, new_node.height, true);
}
else {
Pavel Reznikov's avatar
typo    
Pavel Reznikov committed
354
    alert('Not enough free space to place the widget');
Pavel Reznikov's avatar
Pavel Reznikov committed
355
356
}
```
357
 
Pavel Reznikov's avatar
Pavel Reznikov committed
358

359
360
## Utils

Pavel Reznikov's avatar
add TOC    
Pavel Reznikov committed
361
### GridStackUI.Utils.sort(nodes, dir, width)
362
363
364
365

Sorts array of nodes

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

Pavel Reznikov's avatar
Pavel Reznikov committed
369
370
## Touch devices support

Pavel Reznikov's avatar
Pavel Reznikov committed
371
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
372
373
working on touch-based devices.

Pavel Reznikov's avatar
Pavel Reznikov committed
374
```html
Pavel Reznikov's avatar
Pavel Reznikov committed
375
376
<script src="underscore-min.js"></script>
<script src="jquery.min.js"></script>
Pavel Reznikov's avatar
Pavel Reznikov committed
377
378
379
380
381
382
<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
383
384
385
386
387
388
389
390
391
392
393
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
394
395
396
397
398
## Use with knockout.js

```javascript
ko.components.register('dashboard-grid', {
    viewModel: {
Pavel Reznikov's avatar
Pavel Reznikov committed
399
400
        createViewModel: function (controller, componentInfo) {
            var ViewModel = function (controller, componentInfo) {
Pavel Reznikov's avatar
Pavel Reznikov committed
401
402
                var grid = null;

Pavel Reznikov's avatar
Pavel Reznikov committed
403
                this.widgets = controller.widgets;
Pavel Reznikov's avatar
Pavel Reznikov committed
404
405

                this.afterAddWidget = function (items) {
Pavel Reznikov's avatar
Pavel Reznikov committed
406
407
408
409
410
411
412
413
414
415
416
                    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
417
418
419
                };
            };

Pavel Reznikov's avatar
Pavel Reznikov committed
420
            return new ViewModel(controller, componentInfo);
Pavel Reznikov's avatar
Pavel Reznikov committed
421
422
        }
    },
Pavel Reznikov's avatar
Pavel Reznikov committed
423
424
425
    template:
        [
            '<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">',
Pavel Reznikov's avatar
Pavel Reznikov committed
426
427
            '   <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
428
            '   </div>',
Pavel Reznikov's avatar
Pavel Reznikov committed
429
430
            '</div> '
        ].join('')
Pavel Reznikov's avatar
Pavel Reznikov committed
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
});

$(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
446
447
448
449
450
451
452
453
454
});
```

and HTML:

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

Pavel Reznikov's avatar
Pavel Reznikov committed
455
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
456

Pavel Reznikov's avatar
Pavel Reznikov committed
457
458
459
**Notes:** It's very important to exclude training spaces after widget template:

```
Pavel Reznikov's avatar
Pavel Reznikov committed
460
461
462
463
464
465
466
467
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
468
469
470
471
```

Otherwise `addDisposeCallback` won't work.

Pavel Reznikov's avatar
Pavel Reznikov committed
472

473
474
## Change grid width

475
476
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. 
477

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

```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% }
```

502
503
and so on.

Pavel Reznikov's avatar
Pavel Reznikov committed
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
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
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
## 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
541
542
See example: [Serialization demo](http://troolee.github.io/gridstack.js/demo/serialization.html)

Pavel Reznikov's avatar
Pavel Reznikov committed
543
544
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
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
## 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
570
571
See example: [Serialization demo](http://troolee.github.io/gridstack.js/demo/serialization.html)

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

Pavel Reznikov's avatar
Pavel Reznikov committed
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
## 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
589
590
591
## 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
592
CSS stylesheet dynamically. As a workaround you can do the following:
Pavel Reznikov's avatar
Pavel Reznikov committed
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633

- 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
634

Pavel Reznikov's avatar
Pavel Reznikov committed
635
636
637
638
639
640
641
## 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)


642
643
644
Changes
=======

Pavel Reznikov's avatar
typo    
Pavel Reznikov committed
645
#### v0.2.3 (development version)
Pavel Reznikov's avatar
Pavel Reznikov committed
646

Pavel Reznikov's avatar
Pavel Reznikov committed
647
- add `is_area_empty` method
Pavel Reznikov's avatar
Pavel Reznikov committed
648
- nested grids
649
- add `batch_update`/`commit` methods
Pavel Reznikov's avatar
Pavel Reznikov committed
650
- add `update` method
Pavel Reznikov's avatar
Pavel Reznikov committed
651
- allow to override `resizable`/`draggable` options
Pavel Reznikov's avatar
Pavel Reznikov committed
652
- add `disable`/`enable` methods
Pavel Reznikov's avatar
Pavel Reznikov committed
653
- add `get_cell_from_pixel` (thanks to @juchi)
Pavel Reznikov's avatar
Pavel Reznikov committed
654
- AMD support
Pavel Reznikov's avatar
changes    
Pavel Reznikov committed
655
- fix nodes sorting
Pavel Reznikov's avatar
Pavel Reznikov committed
656
657
- improved touch devices support
- add `always_show_resize_handle` option
Pavel Reznikov's avatar
Pavel Reznikov committed
658
- minor fixes and improvements
Pavel Reznikov's avatar
Pavel Reznikov committed
659

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

Pavel Reznikov's avatar
Pavel Reznikov committed
662
- fix grid initialization
663
- add `cell_height`/`cell_width` API methods
Pavel Reznikov's avatar
Pavel Reznikov committed
664
665
- fix boolean attributes (issue #31)

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

Pavel Reznikov's avatar
Pavel Reznikov committed
668
- add widgets locking (issue #19)
Pavel Reznikov's avatar
Pavel Reznikov committed
669
670
- add `will_it_fit` API method
- fix auto-positioning (issue #20)
Pavel Reznikov's avatar
Pavel Reznikov committed
671
- add animation (thanks to @ishields)
Pavel Reznikov's avatar
Pavel Reznikov committed
672
- fix `y` coordinate calculation when dragging (issue #18)
Pavel Reznikov's avatar
Pavel Reznikov committed
673
- fix `remove_widget` (issue #16)
Pavel Reznikov's avatar
Pavel Reznikov committed
674
- minor fixes
Pavel Reznikov's avatar
Pavel Reznikov committed
675
676


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

Pavel Reznikov's avatar
Pavel Reznikov committed
679
- add `height` option
Pavel Reznikov's avatar
Pavel Reznikov committed
680
- auto-generate css rules (widgets `height` and `top`)
Pavel Reznikov's avatar
Pavel Reznikov committed
681
682
- add `GridStackUI.Utils.sort` utility function
- add `remove_all` API method
683
- add `resize` and `move` API methods 
684
685
- add `resizable` and `movable` API methods
- add `data-gs-no-move` attribute
Pavel Reznikov's avatar
Pavel Reznikov committed
686
687
- add `float` option
- fix default css rule for inner content
Pavel Reznikov's avatar
Pavel Reznikov committed
688
- minor fixes
Pavel Reznikov's avatar
Pavel Reznikov committed
689

690
691
#### v0.1.0 (2014-11-18)

Pavel Reznikov's avatar
Pavel Reznikov committed
692
693
Very first version.

694

Pavel Reznikov's avatar
license    
Pavel Reznikov committed
695
696
697
698
699
License
=======

The MIT License (MIT)

Pavel Reznikov's avatar
Pavel Reznikov committed
700
Copyright (c) 2014-2015 Pavel Reznikov
Pavel Reznikov's avatar
license    
Pavel Reznikov committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719

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.