├── .editorconfig
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular.json
├── demo-dashboard
├── README.md
├── app
│ ├── app.component.ts
│ ├── app.css
│ ├── app.html
│ └── boot.ts
├── index.html
├── package.json
├── style.css
└── tsconfig.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── projects
└── angular2-grid
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── ng-package.prod.json
│ ├── package.json
│ ├── src
│ ├── NgGrid.css
│ ├── NgGrid_FixSmall.css
│ ├── components
│ │ └── NgGridPlaceholder.ts
│ ├── directives
│ │ ├── NgGrid.ts
│ │ └── NgGridItem.ts
│ ├── helpers
│ │ └── NgGridHelpers.ts
│ ├── interfaces
│ │ └── INgGrid.ts
│ ├── main.ts
│ ├── modules
│ │ └── NgGrid.module.ts
│ ├── public_api.ts
│ └── test.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ └── app.module.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.app.json
└── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # IDEs and editors
11 | /.idea
12 | .project
13 | .classpath
14 | .c9/
15 | *.launch
16 | .settings/
17 |
18 | # IDE - VSCode
19 | .vscode/*
20 | !.vscode/settings.json
21 | !.vscode/tasks.json
22 | !.vscode/launch.json
23 | !.vscode/extensions.json
24 |
25 | # misc
26 | /.sass-cache
27 | /connect.lock
28 | /coverage/*
29 | /libpeerconnection.log
30 | npm-debug.log
31 | testem.log
32 | /typings
33 |
34 | # e2e
35 | /e2e/*.js
36 | /e2e/*.map
37 |
38 | #System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [2.3.1](https://github.com/BTMorton/angular2-grid/compare/v2.3.0...v2.3.1) - 2018-07-17
8 | ### Changed
9 | - Exposed NgConfigFixDirection interface ([#294](https://github.com/BTMorton/angular2-grid/issues/294))
10 | - Update Angular peer dependecy to support Angular 5 ([#292](https://github.com/BTMorton/angular2-grid/issues/292))
11 |
12 | ## [2.3.0](https://github.com/BTMorton/angular2-grid/compare/v2.2.4...v2.3.0) - 2018-07-14
13 | ### Added
14 | - Configuration of available resize directions ([#290](https://github.com/BTMorton/angular2-grid/pull/290))
15 | - Item overlap support ([#238](https://github.com/BTMorton/angular2-grid/issues/238))
16 |
17 | ## [2.2.4](https://github.com/BTMorton/angular2-grid/compare/v2.2.3...v2.2.4) - 2018-07-14
18 | ### Fixed
19 | - Error in fix position logic ([#289](https://github.com/BTMorton/angular2-grid/issues/289))
20 |
21 | ## [2.2.3](https://github.com/BTMorton/angular2-grid/compare/v2.2.1...v2.2.3) - 2018-06-11
22 | (Version 2.2.2 was mispublished and did not include any changes)
23 | ### Added
24 | - Code linting
25 |
26 | ## [2.2.1](https://github.com/BTMorton/angular2-grid/compare/v2.2.0...v2.2.1) - 2018-06-10
27 | ### Fixed
28 | - Remove debug logging
29 |
30 | ## [2.2.0](https://github.com/BTMorton/angular2-grid/compare/v2.1.0...v2.2.0) - 2018-06-10
31 | ### Added
32 | - Configuration to specify directions to fix item positions
33 | - Top/left resizing for items ([#191](https://github.com/BTMorton/angular2-grid/issues/191))
34 |
35 | ### Changed
36 | - Handling of items in the grid rework, removing the item grid and replacing it with a set of items currently in the grid ([#267](https://github.com/BTMorton/angular2-grid/pull/267))
37 | - Overhaul algorithm for fixing item positions ([#264](https://github.com/BTMorton/angular2-grid/issues/264))
38 | - Only listen to mouse events when configuration requires it ([#287](https://github.com/BTMorton/angular2-grid/pull/287))
39 |
40 | ### Fixed
41 | - Change detection of user configuration
42 | - Resize detection on item extremities ([#282](https://github.com/BTMorton/angular2-grid/issues/282))
43 |
44 | ## [2.1.0](https://github.com/BTMorton/angular2-grid/compare/v2.0.7...v2.1.0) - 2017-10-22
45 | ### Added
46 | - Option to center items when using `limit_to_screen` ([#261](https://github.com/BTMorton/angular2-grid/pull/261))
47 |
48 | ## [2.0.7](https://github.com/BTMorton/angular2-grid/compare/v2.0.6...v2.0.7) - 2017-09-01
49 | ### Added
50 | - Config option to allow calculation max rows by element size ([#250](https://github.com/BTMorton/angular2-grid/issues/250))
51 |
52 | ### Changed
53 | - Update usage steps in Readme ([#248](https://github.com/BTMorton/angular2-grid/pull/248))
54 |
55 | ### Fixes
56 | - Use explicit exports for better compiler support ([#257](https://github.com/BTMorton/angular2-grid/pull/257))
57 | - Add a check to prevent items being resized larger than the available columns ([#258](https://github.com/BTMorton/angular2-grid/pull/258))
58 | - Improve handling of oversized items ([#260](https://github.com/BTMorton/angular2-grid/issues/260))
59 | - Improve calcuation of drag and resize areas ([#245](https://github.com/BTMorton/angular2-grid/issues/245))
60 |
61 | ## [2.0.6](https://github.com/BTMorton/angular2-grid/compare/v2.0.5...v2.0.6) - 2017-06-28
62 | ### Fixes
63 | - Add verification of item positions when calculating positions
64 |
65 | ## [2.0.5](https://github.com/BTMorton/angular2-grid/compare/v2.0.4...v2.0.5) - 2017-06-25
66 | ### Changed
67 | - Update peer dependencies to support Angular v4
68 |
69 | ## [2.0.4](https://github.com/BTMorton/angular2-grid/compare/v2.0.3...v2.0.4) - 2017-06-25
70 | ### Added
71 | - Add public method to trigger a resize
72 |
73 | ### Changed
74 | - Use absolute positioning instead of css transform ([#241](https://github.com/BTMorton/angular2-grid/issues/241))
75 |
76 | ### Fixed
77 | - Revert a bug caused by a change when removing items ([#242](https://github.com/BTMorton/angular2-grid/issues/242))
78 | - Fix Firefox dragHandle issue ([#225](https://github.com/BTMorton/angular2-grid/issues/225))
79 | - Remove debug logging
80 |
81 | ## [2.0.3](https://github.com/BTMorton/angular2-grid/compare/v2.0.2...v2.0.3) - 2017-06-03
82 | ### Changed
83 | - Use [ngGrid] binding instead of [ng-grid] in readme ([#205](https://github.com/BTMorton/angular2-grid/pull/205))
84 |
85 | ### Fixed
86 | - Several issues with `limit_to_screen` property
87 |
88 | ## [2.0.2](https://github.com/BTMorton/angular2-grid/compare/v2.0.1...v2.0.2) - 2017-03-05
89 | ### Added
90 | - Gitter link in README ([#198](https://github.com/BTMorton/angular2-grid/issues/198))
91 | - Change log ([#195](https://github.com/BTMorton/angular2-grid/issues/195))
92 |
93 | ### Fixed
94 | - Null item bug in dragStart ([#208](https://github.com/BTMorton/angular2-grid/issues/208))
95 | - Touch events freezing on iDevices ([#190](https://github.com/BTMorton/angular2-grid/issues/190))
96 | - `limit_to_screen` property not being honoured when adding new items ([#199](https://github.com/BTMorton/angular2-grid/issues/199))
97 |
98 | ## [2.0.1](https://github.com/BTMorton/angular2-grid/compare/v1.1.0...v2.0.1) - 2017-01-26
99 | ### Changed
100 | - Distributed bundle files
101 | - Build process
102 |
103 | ## [1.0.1](https://github.com/BTMorton/angular2-grid/compare/v1.0.0...v1.0.1) - 2017-01-24 [YANKED]
104 | ### Added
105 | - Additional demo page ([#177](https://github.com/BTMorton/angular2-grid/pull/177))
106 |
107 | ### Changed
108 | - Updated readme and demo
109 | - Include margin values for row height calculations ([#174](https://github.com/BTMorton/angular2-grid/pull/174))
110 | - Make host methods public ([#176](https://github.com/BTMorton/angular2-grid/pull/176))
111 |
112 | ### Fixed
113 | - Null item bug in resizeStart ([#185](https://github.com/BTMorton/angular2-grid/pull/185))
114 |
115 | ## [1.0.0](https://github.com/BTMorton/angular2-grid/compare/v0.11.2...v1.0.0) - 2015-01-24 [YANKED]
116 | ### Changed
117 | - Distributed bundle files
118 |
119 | ## [0.11.2](https://github.com/BTMorton/angular2-grid/compare/v0.11.1...v0.11.2) - 2016-11-25
120 | ### Fixed
121 | - Errors caused by debugging in old commit ([#164](https://github.com/BTMorton/angular2-grid/issues/164))
122 |
123 | ## [0.11.1](https://github.com/BTMorton/angular2-grid/compare/v0.11.0...v0.11.1) - 2016-11-22
124 | ### Changed
125 | - Item change events now also trigger on add item and remove item ([#134](https://github.com/BTMorton/angular2-grid/issues/134))
126 | - Reduced default item drag/resize border size from 50 to 25
127 |
128 | ### Fixed
129 | - Dragging in safari ([#137](https://github.com/BTMorton/angular2-grid/issues/137))
130 |
131 | ## [0.11.0](https://github.com/BTMorton/angular2-grid/compare/v0.10.0...v0.11.0) - 2016-11-17
132 | ### Changed
133 | - Updated to Angular version 2.0.0
134 |
135 | ## [0.10.0](https://github.com/BTMorton/angular2-grid/compare/v0.9.1...v0.10.0) - 2016-09-13
136 |
137 | ## [0.9.1](https://github.com/BTMorton/angular2-grid/compare/v0.9.0...v0.9.1) - 2016-07-30
138 |
139 | ## [0.9.0](https://github.com/BTMorton/angular2-grid/compare/v0.8.3....v0.9.0) - 2016-07-25
140 |
141 |
142 | // TODO: Complete changelog with full history
143 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Ben Morton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/BTMorton/angular2-grid)
2 | [](https://www.npmjs.com/package/angular2-grid)
3 | [](https://libraries.io/bower/angular2-grid)
4 | [](https://github.com/BTMorton/angular2-grid/blob/master/LICENSE)
5 | [](https://github.com/BTMorton/angular2-grid/issues)
6 | [](https://gitter.im/angular2grid/Lobby)
7 |
8 | # Angular 2 Grid
9 | Angular 2 grid is a drag/drop/resize grid-based plugin directive for [Angular 2](http://angular.io).
10 | The demo included in this repo follows the [Angular 2 quick start](https://angular.io/docs/js/latest/quickstart.html)
11 |
12 | #### Setup
13 | ----------
14 |
15 | To use the Angular 2 Grid system, simply run `npm install angular2-grid` and then include the `NgGridModule` in your project module imports (see Example for more details).
16 |
17 | If you want to help with development or try the demo, it's less simple, but not hard. First you'll need to install [Node](http://nodejs.org) and check out a copy of the repo. Then run:
18 |
19 | ```shell
20 | $ npm install
21 | $ typings install
22 | $ gulp build
23 | ```
24 |
25 | This will give you a fully compiled version of the demo that you can run using the HTTP server of your choice.
26 |
27 | You can also use `gulp watch` to compile the demo and have gulp watch for any changes.
28 |
29 | NOTE: By default Angular 2 and System.js are not listed as actual dependencies, but as peer dependencies, so that npm doesn't install them on systems that just require the install file. If they are not installed, this could cause gulp to break. To fix this, run `npm install angular2 systemjs` and rerun the build command.
30 |
31 | #### Config
32 | -----------
33 |
34 | To use this in your own application, all you need to do is add the `[ngGrid]` attribute to your container element and `[ngGridItem]` to each item. You can use this in conjunction with `NgFor` to create a truly dynamic angular grid.
35 |
36 | To configure the grid with your own options, it is as easy as adding them as the attribute value. The defaults for the grid are:
37 |
38 | ```javascript
39 | {
40 | 'margins': [10], // The size of the margins of each item. Supports up to four values in the same way as CSS margins. Can be updated using setMargins()
41 | 'draggable': true, // Whether the items can be dragged. Can be updated using enableDrag()/disableDrag()
42 | 'resizable': true, // Whether the items can be resized. Can be updated using enableResize()/disableResize()
43 | 'max_cols': 0, // The maximum number of columns allowed. Set to 0 for infinite. Cannot be used with max_rows
44 | 'max_rows': 0, // The maximum number of rows allowed. Set to 0 for infinite. Cannot be used with max_cols
45 | 'visible_cols': 0, // The number of columns shown on screen when auto_resize is set to true. Set to 0 to not auto_resize. Will be overriden by max_cols
46 | 'visible_rows': 0, // The number of rows shown on screen when auto_resize is set to true. Set to 0 to not auto_resize. Will be overriden by max_rows
47 | 'min_cols': 0, // The minimum number of columns allowed. Can be any number greater than or equal to 1.
48 | 'min_rows': 0, // The minimum number of rows allowed. Can be any number greater than or equal to 1.
49 | 'col_width': 250, // The width of each column
50 | 'row_height': 250, // The height of each row
51 | 'cascade': 'up', // The direction to cascade grid items ('up', 'right', 'down', 'left')
52 | 'min_width': 100, // The minimum width of an item. If greater than col_width, this will update the value of min_cols
53 | 'min_height': 100, // The minimum height of an item. If greater than row_height, this will update the value of min_rows
54 | 'fix_to_grid': false, // Fix all item movements to the grid
55 | 'auto_style': true, // Automatically add required element styles at run-time
56 | 'auto_resize': false, // Automatically set col_width/row_height so that max_cols/max_rows fills the screen. Only has effect is max_cols or max_rows is set
57 | 'maintain_ratio': false, // Attempts to maintain aspect ratio based on the colWidth/rowHeight values set in the config
58 | 'prefer_new': false, // When adding new items, will use that items position ahead of existing items
59 | 'limit_to_screen': false, // When resizing the screen, with this true and auto_resize false, the grid will re-arrange to fit the screen size. Please note, at present this only works with cascade direction up.
60 | 'center_to_screen': false, // When resizing the screen, with this true and limit_to_screen true, the grid will center itself to the screen if max columns width is smaller than the grid width.
61 | 'resize_directions': [ // The enabled resize directions in priority order
62 | "bottomright", // If two directions overlap, e.g. bottomright and right, the direction that appears first in this list will be used
63 | "bottomleft",
64 | "topright",
65 | "topleft",
66 | "right",
67 | "left",
68 | "bottom",
69 | "top"
70 | ],
71 | }
72 | ```
73 |
74 | The defaults for the grid item are:
75 |
76 | ```javascript
77 | {
78 | 'col': 1, // The start column for the item
79 | 'row': 1, // The start row for the item
80 | 'sizex': 1, // The start width in terms of columns for the item
81 | 'sizey': 1, // The start height in terms of rows for the item
82 | 'dragHandle': null, // The selector to be used for the drag handle. If null, uses the whole item
83 | 'resizeHandle': null, // The selector to be used for the resize handle. If null, uses 'borderSize' pixels from the right for horizontal resize,
84 | // 'borderSize' pixels from the bottom for vertical, and the square in the corner bottom-right for both
85 | 'borderSize': 15,
86 | 'fixed': false, // If the grid item should be cascaded or not. If yes, manual movement is required
87 | 'draggable': true, // If the grid item can be dragged. If this or the global setting is set to false, the item cannot be dragged.
88 | 'resizable': true, // If the grid item can be resized. If this or the global setting is set to false, the item cannot be resized.
89 | 'payload': null, // An optional custom payload (string/number/object) to be used to identify the item for serialization
90 | 'maxCols': 0, // The maximum number of columns for a particular item. This value will only override the value from the grid (if set) if it is smaller
91 | 'minCols': 0, // The minimum number of columns for a particular item. This value will only override the value from the grid if larger
92 | 'maxRows': 0, // The maximum number of rows for a particular item. This value will only override the value from the grid (if set) if it is smaller
93 | 'minRows': 0, // The minimum number of rows for a particular item. This value will only override the value from the grid if larger
94 | 'minWidth': 0, // The minimum width of a particular item. This value will override the value from the grid, as well as the minimum columns if the resulting size is larger
95 | 'minHeight': 0, // The minimum height of a particular item. This value will override the value from the grid, as well as the minimum rows if the resulting size is larger
96 | 'resizeDirections': null, // The enabled resize directions in priority order. If null, uses the 'resize_directions' for the grid
97 | }
98 | ```
99 |
100 | #### Event Handling
101 | -------------------
102 |
103 | Both the `NgGrid` and `NgGridItem` throw events when an item is moved or resized. The grid has the following:
104 |
105 | ```javascript
106 | onDragStart(item) // When an item starts being dragged. Returns reference to corresponding NgGridItem
107 | onDrag(item) // When an item moves while dragging. Returns reference to corresponding NgGridItem
108 | onDragStop(item) // When an item stops being dragged. Returns reference to corresponding NgGridItem
109 | onResizeStart(item) // When an item starts being resized. Returns reference to corresponding NgGridItem
110 | onResize(item) // When an item is resized. Returns reference to corresponding NgGridItem
111 | onResizeStop(item) // When an item stops being resized. Returns reference to corresponding NgGridItem
112 | onItemChange(items) // When any item stops being dragged or resized. Returns an array of NgGridItemEvents in the order in which each item was added to the grid
113 | ```
114 |
115 | The individual items will also throw the following events:
116 |
117 | ```javascript
118 | onDragStart() // When the item starts being dragged.
119 | onDrag() // When the item moves while dragging.
120 | onDragStop() // When the item stops being dragged.
121 | onDragAny() // When the item starts/stops/is being dragged.
122 | onResizeStart() // When the item starts being resized.
123 | onResize() // When the item is resized.
124 | onResizeStop() // When the item stops being resized.
125 | onResizeAny() // When the item starts/stops/is being resized.
126 | onChangeStart() // When the item starts being dragged or resized.
127 | onChange() // When the item is dragged or resized.
128 | onChangeStop() // When the item stops being dragged or resized.
129 | onChangeAny() // When the item starts/stops/is being dragged or resized.
130 | onItemChange() // When either the item's grid size or position is changed.
131 | ```
132 |
133 | Each event will also provide the following object to any callback functions:
134 |
135 | ```javascript
136 | interface NgGridItemEvent {
137 | payload: any, // The item's optional custom payload (string/number/object) to be used to identify the item for serialization
138 | col: number, // The item's column position within the grid
139 | row: number, // The item's row position within the grid
140 | sizex: number, // The item's column size within the grid
141 | sizey: number, // The item's row size within the grid
142 | width: number, // The item's raw width value
143 | height: number, // The item's raw height value
144 | left: number, // The item's offset left value
145 | top: number // The item's offset top value
146 | }
147 | ```
148 |
149 | #### Styling
150 | ------------
151 |
152 | There are three elements that can be styled with angular2-grid, the grid itself `.grid`, the items `.grid-item` and the placeholder `.placeholder`. The demo includes some basic styling in NgGrid.css which you can include in your app's `styleUrls` property. It also includes some @media queries styles to handle responsiveness on smaller screens. This simple force the boxes to full width and puts them inline in their original order. This is optional functionality and does not need to be included. In order for correct functionality, the required styles are added by the classes themselves at run-time:
153 |
154 | ```css
155 | .grid {
156 | position: relative;
157 | }
158 |
159 | .grid-item {
160 | position: absolute;
161 | }
162 |
163 | .grid-item.moving {
164 | z-index: z-index + 1;
165 | }
166 |
167 | .placeholder {
168 | position: absolute;
169 | }
170 | ```
171 |
172 | You can prevent these styles being automatically added by setting the value of `'auto_size'` to be `false`. You will then need to ensure that they are correctly incorporated into your user styles instead.
173 |
174 | NOTE: The grid system sets the values `width, height, left, top` in CSS to move and resize the elements. This cannot be disabled.
175 |
176 | #### Example
177 | ------------
178 |
179 | The `NgGrid` and `NgGridItem` can be configured by binding directly to the directive. The `NgGridItem` supports two-way binding so you don't need to bind to any of the above events. The `NgGridItemChange` event emits under the same conditions as `onChangeStop`. The only config values that will change are `col`, `row`, `sizex` and `sizey`; the rest of your configuration will persist. You can then use these values for serialization of the grid. By binding the configuration this way, you are able to update the values on the fly. Here is an example template of the grid with two-way item bindings:
180 |
181 | ```html
182 |
183 |
184 |
{{box.title}}
185 |
{{box.text}}
186 |
187 |
188 | ```
189 |
190 | In order to include the relevant files, you will need to import the `NgGridModule` to your app and add them to the `@NgModule` imports. This can be achieved by adding:
191 |
192 | ```typescript
193 | import { NgGridModule } from 'angular2-grid';
194 | ```
195 |
196 | to your typescript imports, and ensuring that your `@NgModule` annotation looks similar to the following:
197 |
198 | ```typescript
199 | @NgModule({
200 | ...,
201 | imports: [
202 | ...,
203 | NgGridModule,
204 | ...
205 | ],
206 | ...
207 | })
208 | ```
209 |
210 | As of the Angular 2 Release Candidate you will now need to have the following in your System.js configuration, assuming that you are following the same format as the [Angular 2 Quick Start](https://angular.io/docs/ts/latest/quickstart.html):
211 |
212 | ```
213 | // latest version of angular2-grid
214 | map: {
215 | 'angular2-grid': 'node_modules/angular2-grid/bundles'
216 | }
217 |
218 | packages: {
219 | 'angular2-grid': { main: 'NgGrid.umd.js', defaultExtension: 'js' }
220 | }
221 |
222 | // earlier versions
223 | map: {
224 | 'angular2-grid': 'node_modules/angular2-grid/dist/js'
225 | }
226 |
227 | packages: {
228 | 'angular2-grid': { main: 'main.js', defaultExtension: 'js' }
229 | }
230 | ```
231 |
232 | Alternatively, for earlier versions you can use the bundled version by setting the `map` value to `'node_modules/angular2-grid/dist/bundles'` and the `main` value within packages to `NgGrid.min.js`.
233 |
234 | To see a working typescript example project, check the [dashboard demo folder in the source](https://github.com/BTMorton/angular2-grid/tree/master/demo-dashboard) or the [main demo repository](https://github.com/BTMorton/angular2-grid-demo).
235 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular2-grid-demo": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist/angular2-grid-demo",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets"
21 | ],
22 | "styles": [
23 | "src/styles.css",
24 | "node_modules/bootstrap/dist/css/bootstrap.min.css",
25 | "dist/angular2-grid/NgGrid.css"
26 | ],
27 | "scripts": []
28 | },
29 | "configurations": {
30 | "production": {
31 | "optimization": true,
32 | "outputHashing": "all",
33 | "sourceMap": false,
34 | "extractCss": true,
35 | "namedChunks": false,
36 | "aot": true,
37 | "extractLicenses": true,
38 | "vendorChunk": false,
39 | "buildOptimizer": true,
40 | "fileReplacements": [
41 | {
42 | "replace": "src/environments/environment.ts",
43 | "with": "src/environments/environment.prod.ts"
44 | }
45 | ]
46 | }
47 | }
48 | },
49 | "serve": {
50 | "builder": "@angular-devkit/build-angular:dev-server",
51 | "options": {
52 | "browserTarget": "angular2-grid-demo:build"
53 | },
54 | "configurations": {
55 | "production": {
56 | "browserTarget": "angular2-grid-demo:build:production"
57 | }
58 | }
59 | },
60 | "extract-i18n": {
61 | "builder": "@angular-devkit/build-angular:extract-i18n",
62 | "options": {
63 | "browserTarget": "angular2-grid-demo:build"
64 | }
65 | },
66 | "test": {
67 | "builder": "@angular-devkit/build-angular:karma",
68 | "options": {
69 | "main": "src/test.ts",
70 | "karmaConfig": "./karma.conf.js",
71 | "polyfills": "src/polyfills.ts",
72 | "scripts": [],
73 | "styles": [
74 | "src/styles.scss"
75 | ],
76 | "assets": [
77 | "src/assets"
78 | ]
79 | }
80 | },
81 | "lint": {
82 | "builder": "@angular-devkit/build-angular:tslint",
83 | "options": {
84 | "tsConfig": [
85 | "src/tsconfig.app.json",
86 | "src/tsconfig.spec.json"
87 | ],
88 | "exclude": []
89 | }
90 | }
91 | }
92 | },
93 | "angular2-grid-demo-e2e": {
94 | "root": "",
95 | "sourceRoot": "e2e",
96 | "projectType": "application",
97 | "architect": {
98 | "e2e": {
99 | "builder": "@angular-devkit/build-angular:protractor",
100 | "options": {
101 | "protractorConfig": "./protractor.conf.js",
102 | "devServerTarget": "angular2-grid-demo:serve"
103 | }
104 | },
105 | "lint": {
106 | "builder": "@angular-devkit/build-angular:tslint",
107 | "options": {
108 | "tsConfig": [
109 | "e2e/tsconfig.e2e.json"
110 | ],
111 | "exclude": []
112 | }
113 | }
114 | }
115 | },
116 | "angular2-grid": {
117 | "root": "projects/angular2-grid",
118 | "sourceRoot": "projects/angular2-grid/src",
119 | "projectType": "library",
120 | "prefix": "lib",
121 | "architect": {
122 | "build": {
123 | "builder": "@angular-devkit/build-ng-packagr:build",
124 | "options": {
125 | "tsConfig": "projects/angular2-grid/tsconfig.lib.json",
126 | "project": "projects/angular2-grid/ng-package.json"
127 | },
128 | "configurations": {
129 | "production": {
130 | "project": "projects/angular2-grid/ng-package.prod.json"
131 | }
132 | }
133 | },
134 | "test": {
135 | "builder": "@angular-devkit/build-angular:karma",
136 | "options": {
137 | "main": "projects/angular2-grid/src/test.ts",
138 | "tsConfig": "projects/angular2-grid/tsconfig.spec.json",
139 | "karmaConfig": "projects/angular2-grid/karma.conf.js"
140 | }
141 | },
142 | "lint": {
143 | "builder": "@angular-devkit/build-angular:tslint",
144 | "options": {
145 | "tsConfig": [
146 | "projects/angular2-grid/tsconfig.lib.json",
147 | "projects/angular2-grid/tsconfig.spec.json"
148 | ],
149 | "exclude": [
150 | "**/node_modules/**"
151 | ]
152 | }
153 | }
154 | }
155 | }
156 | },
157 | "defaultProject": "angular2-grid-demo",
158 | "schematics": {
159 | "@schematics/angular:component": {
160 | "inlineTemplate": false,
161 | "spec": true,
162 | "prefix": "app",
163 | "styleext": "css"
164 | },
165 | "@schematics/angular:directive": {
166 | "prefix": "app"
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/demo-dashboard/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Dashboard demo of angular2-grid
3 |
4 | This is a demo of the angular2-grid project with a new coloring scheme, and the functionality of removing a widget when clicking the corresponing remove icon on widget level.
5 |
6 | #### Setup
7 | ----------
8 |
9 | ```shell
10 | cd demo-dashboard
11 | npm install
12 | npm start
13 | ```
14 |
15 | #### Output
16 | ----------
17 |
18 | 
19 |
--------------------------------------------------------------------------------
/demo-dashboard/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, ViewEncapsulation} from '@angular/core';
2 | import {NgGrid, NgGridItem, NgGridConfig, NgGridItemConfig, NgGridItemEvent} from 'angular2-grid';
3 |
4 | interface Box {
5 | id: number;
6 | config: any;
7 | }
8 |
9 | @Component({
10 | selector: 'my-app',
11 | templateUrl: 'app/app.html',
12 | styleUrls: ['app/app.css'],
13 | encapsulation: ViewEncapsulation.None
14 | })
15 | export class AppComponent {
16 | private boxes: Array = [];
17 | private rgb: string = '#efefef';
18 | private curNum;
19 | private gridConfig: NgGridConfig = {
20 | 'margins': [5],
21 | 'draggable': true,
22 | 'resizable': true,
23 | 'max_cols': 0,
24 | 'max_rows': 0,
25 | 'visible_cols': 0,
26 | 'visible_rows': 0,
27 | 'min_cols': 1,
28 | 'min_rows': 1,
29 | 'col_width': 2,
30 | 'row_height': 2,
31 | 'cascade': 'up',
32 | 'min_width': 50,
33 | 'min_height': 50,
34 | 'fix_to_grid': false,
35 | 'auto_style': true,
36 | 'auto_resize': false,
37 | 'maintain_ratio': false,
38 | 'prefer_new': false,
39 | 'zoom_on_drag': false,
40 | 'limit_to_screen': true
41 | };
42 | private itemPositions: Array = [];
43 |
44 | constructor() {
45 | const dashconf = this._generateDefaultDashConfig();
46 | for (var i = 0; i < dashconf.length; i++) {
47 | const conf = dashconf[i];
48 | conf.payload = 1 + i;
49 | this.boxes[i] = { id: i + 1, config: conf };
50 | }
51 | this.curNum = dashconf.length + 1;
52 | }
53 |
54 | addBox(): void {
55 | const conf: NgGridItemConfig = this._generateDefaultItemConfig();
56 | conf.payload = this.curNum++;
57 | this.boxes.push({ id: conf.payload, config: conf });
58 | }
59 |
60 | removeWidget(index: number): void {
61 | if (this.boxes[index]) {
62 | this.boxes.splice(index, 1);
63 | }
64 | }
65 |
66 | updateItem(index: number, event: NgGridItemEvent): void {
67 | // Do something here
68 | }
69 |
70 | onDrag(index: number, event: NgGridItemEvent): void {
71 | // Do something here
72 | }
73 |
74 | onResize(index: number, event: NgGridItemEvent): void {
75 | // Do something here
76 | }
77 |
78 | private _generateDefaultItemConfig(): NgGridItemConfig {
79 | return { 'dragHandle': '.handle', 'col': 1, 'row': 1, 'sizex': 1, 'sizey': 1 };
80 | }
81 |
82 | private _generateDefaultDashConfig(): NgGridItemConfig[] {
83 | return [{ 'dragHandle': '.handle', 'col': 1, 'row': 1, 'sizex': 50, 'sizey': 40 },
84 | { 'dragHandle': '.handle', 'col': 1, 'row': 1, 'sizex': 1, 'sizey': 1 },
85 | { 'dragHandle': '.handle', 'col': 26, 'row': 1, 'sizex': 1, 'sizey': 1 },
86 | { 'dragHandle': '.handle', 'col': 51, 'row': 1, 'sizex': 75, 'sizey': 1 },
87 | { 'dragHandle': '.handle', 'col': 51, 'row': 26, 'sizex': 32, 'sizey': 40 },
88 | { 'dragHandle': '.handle', 'col': 83, 'row': 26, 'sizex': 1, 'sizey': 1 }];
89 | }
90 | }
--------------------------------------------------------------------------------
/demo-dashboard/app/app.css:
--------------------------------------------------------------------------------
1 | .grid-item {
2 | border: solid 1px #ccc;
3 | }
4 |
5 | .handle {
6 | width: 100%;
7 | height: 50px;
8 | background: #eee;
9 | color: black;
10 | font-weight: 500;
11 | padding: 15px;
12 | box-sizing: border-box;
13 | }
14 |
15 | .btn {
16 | margin-left: 10px;
17 | }
18 |
19 | .box-header-btns {
20 | top: 15px;
21 | right: 10px;
22 | cursor: pointer;
23 | position: absolute;
24 | }
25 |
26 | .navbar-default {
27 | background: black;
28 | border: 0px;
29 | }
30 |
31 | .navbar-default .navbar-brand {
32 | color: white;
33 | }
34 |
35 | body {
36 | background-color: #f5f2f2;
37 | }
--------------------------------------------------------------------------------
/demo-dashboard/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
Widget {{box.id}}
19 |
20 |
--------------------------------------------------------------------------------
/demo-dashboard/app/boot.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, enableProdMode } from '@angular/core'
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
3 | import { BrowserModule } from '@angular/platform-browser'
4 | import { AppComponent } from './app.component'
5 | import { NgGridModule } from 'angular2-grid';
6 |
7 | @NgModule({
8 | imports: [ BrowserModule, NgGridModule ],
9 | declarations: [ AppComponent ],
10 | providers: [],
11 | bootstrap: [ AppComponent ]
12 | })
13 | class AppModule { }
14 |
15 | //enableProdMode();
16 | const platform = platformBrowserDynamic();
17 | platform.bootstrapModule(AppModule);
--------------------------------------------------------------------------------
/demo-dashboard/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Angular 2 QuickStart
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
33 |
34 |
35 |
36 |
37 |
38 | Loading...
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/demo-dashboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-quickstart",
3 | "scripts": {
4 | "tsc": "tsc",
5 | "tsc:w": "tsc -w",
6 | "lite": "lite-server",
7 | "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
8 | },
9 | "license": "ISC",
10 | "dependencies": {
11 | "@angular/common": "2.4.4",
12 | "@angular/compiler": "2.4.4",
13 | "@angular/core": "2.4.4",
14 | "@angular/platform-browser": "2.4.4",
15 | "@angular/platform-browser-dynamic": "2.4.4",
16 | "angular2-grid": "^2.0.0",
17 | "es6-shim": "^0.35.1",
18 | "reflect-metadata": "^0.1.3",
19 | "rxjs": "5.0.1",
20 | "systemjs": "0.19.29",
21 | "zone.js": "^0.7.2"
22 | },
23 | "devDependencies": {
24 | "concurrently": "^2.1.0",
25 | "lite-server": "^2.2.0",
26 | "typescript": "^2.0.10"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo-dashboard/style.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | background-color: #f5f2f2;
3 | width: 100%;
4 | min-height: 750px;
5 | }
6 |
7 | .grid-item {
8 | background-color: #ffffff;
9 | -webkit-transition: width 0.25s, height 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s;
10 | -moz-transition: width 0.25s, height 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s;
11 | -o-transition: width 0.25s, height 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s;
12 | transition: width 0.25s, height 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s;
13 | border: solid 1px;
14 | }
15 |
16 | .grid-item:active, .grid-item.moving {
17 | z-index: 2;
18 | -webkit-transition: none;
19 | -moz-transition: none;
20 | -o-transition: none;
21 | transition: none;
22 | }
23 |
24 | .grid-placeholder {
25 | background-color: rgba(0, 100, 200, 0.3);
26 | }
27 |
28 | @media (max-width: 767px) {
29 | .grid {
30 | width: 100% !important;
31 | height: auto !important;
32 | padding: 10px;
33 | }
34 | .grid-item {
35 | position: static !important;
36 | width: 100% !important;
37 | margin-bottom: 10px;
38 | }
39 | .grid-item:last-child {
40 | margin-bottom: 0;
41 | }
42 | }
--------------------------------------------------------------------------------
/demo-dashboard/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "system",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "removeComments": false,
10 | "noImplicitAny": false,
11 | "lib": [
12 | "es2015",
13 | "dom"
14 | ]
15 | },
16 | "exclude": [
17 | "node_modules"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Angular2GridDemoPage } from './app.po';
2 |
3 | describe('angular2-grid-demo App', function() {
4 | let page: Angular2GridDemoPage;
5 |
6 | beforeEach(() => {
7 | page = new Angular2GridDemoPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class Angular2GridDemoPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2017"
10 | ],
11 | "outDir": "../out-tsc/e2e",
12 | "module": "commonjs",
13 | "target": "es5",
14 | "types":[
15 | "jasmine",
16 | "node"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('~angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | files: [
19 |
20 | ],
21 | preprocessors: {
22 |
23 | },
24 | mime: {
25 | 'text/x-typescript': ['ts','tsx']
26 | },
27 | coverageIstanbulReporter: {
28 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
29 | fixWebpackSourcePaths: true
30 | },
31 |
32 | reporters: config.angularCli && config.angularCli.codeCoverage
33 | ? ['progress', 'coverage-istanbul']
34 | : ['progress', 'kjhtml'],
35 | port: 9876,
36 | colors: true,
37 | logLevel: config.LOG_INFO,
38 | autoWatch: true,
39 | browsers: ['Chrome'],
40 | singleRun: false
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-grid-demo",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "angular-cli": {},
6 | "scripts": {
7 | "ng": "ng",
8 | "start": "ng serve",
9 | "build": "ng build",
10 | "build-prod": "ng build --prod",
11 | "build-lib": "ng build --prod angular2-grid && npm run copy-lib",
12 | "copy-lib": "copyfiles -f LICENSE README.md projects/angular2-grid/src/*.css dist/angular2-grid",
13 | "test": "ng test",
14 | "lint": "ng lint",
15 | "e2e": "ng e2e"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/common": "^9.0.1",
20 | "@angular/compiler": "^9.0.1",
21 | "@angular/core": "^9.0.1",
22 | "@angular/forms": "^9.0.1",
23 | "@angular/http": "^7.2.16",
24 | "@angular/platform-browser": "^9.0.1",
25 | "@angular/platform-browser-dynamic": "^9.0.1",
26 | "@angular/router": "^9.0.1",
27 | "bootstrap": "^3.3.7",
28 | "core-js": "^2.5.7",
29 | "rxjs": "^6.5.4",
30 | "zone.js": "^0.10.2"
31 | },
32 | "devDependencies": {
33 | "@angular-devkit/build-angular": "~0.900.2",
34 | "@angular-devkit/build-ng-packagr": "~0.900.2",
35 | "@angular/cli": "^9.0.2",
36 | "@angular/compiler-cli": "^9.0.1",
37 | "@types/jasmine": "3.5.3",
38 | "@types/node": "~13.7.1",
39 | "codelyzer": "~5.2.1",
40 | "copyfiles": "^2.2.0",
41 | "jasmine-core": "~3.5.0",
42 | "jasmine-spec-reporter": "~4.2.1",
43 | "karma": "~4.4.1",
44 | "karma-chrome-launcher": "~3.1.0",
45 | "karma-cli": "~2.0.0",
46 | "karma-coverage-istanbul-reporter": "^2.1.1",
47 | "karma-jasmine": "~3.1.1",
48 | "karma-jasmine-html-reporter": "^1.5.2",
49 | "ng-packagr": "^9.0.0",
50 | "protractor": "~5.4.3",
51 | "ts-node": "~8.6.2",
52 | "tsickle": "0.38.0",
53 | "tslib": "^1.10.0",
54 | "tslint": "^6.0.0",
55 | "typescript": "~3.7.5"
56 | }
57 | }
--------------------------------------------------------------------------------
/projects/angular2-grid/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/projects/angular2-grid/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/angular2-grid",
4 | "deleteDestPath": false,
5 | "lib": {
6 | "entryFile": "src/main.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/projects/angular2-grid/ng-package.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/angular2-grid",
4 | "lib": {
5 | "entryFile": "src/main.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/projects/angular2-grid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-grid",
3 | "version": "4.0.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/BTMorton/angular2-grid.git"
7 | },
8 | "keywords": [
9 | "angular",
10 | "angular2",
11 | "grid",
12 | "web-components"
13 | ],
14 | "author": "Ben Morton (http://bmorton.co.uk/)",
15 | "contributors": [
16 | "Martin Sikora (https://github.com/martinsik)",
17 | "Daniel Pastor de la Fuenta (https://github.com/aebsubis)",
18 | "Frederik Schubert (https://github.com/frederikschubert)",
19 | "Matthew de Nobrega (https://github.com/matthewdenobrega)",
20 | "Alex (https://github.com/lifecoderua)",
21 | "pocmanu (https://github.com/pocmanu)",
22 | "Chris Morabito (https://github.com/morabitowoolpert)",
23 | "Carlos Esteban Lopez Jaramillo (https://github.com/Luchillo)",
24 | "Niklas Berg (https://github.com/nkholski)",
25 | "Wouter (https://github.com/wuhkuh)",
26 | "Brian Ellis (https://github.com/OnlyAGhost)",
27 | "Janne Julkunen (https://github.com/sconix)",
28 | "Thomas Schellenberg (https://github.com/ThomasSchellenbergNextCentury)"
29 | ],
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/BTMorton/angular2-grid/issues"
33 | },
34 | "homepage": "https://github.com/BTMorton/angular2-grid",
35 | "peerDependencies": {
36 | "@angular/common": "^8.0.0",
37 | "@angular/core": "^8.0.0"
38 | }
39 | }
--------------------------------------------------------------------------------
/projects/angular2-grid/src/NgGrid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | background-color: #efefef;
3 | transform-origin: top center;
4 | transition: transform 0.5s;
5 | }
6 |
7 | .grid-item {
8 | background-color: #ffffff;
9 | -webkit-transition: width 0.25s, height 0.25s, transform 0.5s;
10 | -moz-transition: width 0.25s, height 0.25s, transform 0.5s;
11 | -o-transition: width 0.25s, height 0.25s, transform 0.5s;
12 | transition: width 0.25s, height 0.25s, transform 0.5s;
13 | overflow: hidden;
14 | }
15 |
16 | .grid-item:active, .grid-item.moving {
17 | z-index: 2;
18 | -webkit-transition: none;
19 | -moz-transition: none;
20 | -o-transition: none;
21 | transition: none;
22 | }
23 |
24 | .grid-placeholder {
25 | background-color: rgba(0, 0, 0, 0.3);
26 | }
--------------------------------------------------------------------------------
/projects/angular2-grid/src/NgGrid_FixSmall.css:
--------------------------------------------------------------------------------
1 | @media (max-width: 767px) {
2 | .grid {
3 | width: 100% !important;
4 | height: auto !important;
5 | padding: 10px;
6 | }
7 | .grid-item {
8 | position: static !important;
9 | width: 100% !important;
10 | margin-bottom: 10px;
11 | transform: none !important;
12 | }
13 | .grid-item:last-child {
14 | margin-bottom: 0;
15 | }
16 | }
--------------------------------------------------------------------------------
/projects/angular2-grid/src/components/NgGridPlaceholder.ts:
--------------------------------------------------------------------------------
1 | import { NgGrid } from '../directives/NgGrid';
2 | import { NgGridItemPosition, NgGridItemSize } from '../interfaces/INgGrid';
3 | import { Component, Directive, ElementRef, Renderer2, EventEmitter, Host, ViewEncapsulation, Type, ComponentRef, KeyValueDiffer, KeyValueDiffers, OnInit, OnDestroy, DoCheck, ViewContainerRef, Output } from '@angular/core';
4 |
5 | @Component({
6 | selector: 'ng-grid-placeholder',
7 | template: ''
8 | })
9 | export class NgGridPlaceholder implements OnInit {
10 | private _size: NgGridItemSize;
11 | private _position: NgGridItemPosition;
12 | private _ngGrid: NgGrid;
13 | private _cascadeMode: string;
14 |
15 | constructor(private _ngEl: ElementRef, private _renderer: Renderer2) { }
16 |
17 | public registerGrid(ngGrid: NgGrid) {
18 | this._ngGrid = ngGrid;
19 | }
20 |
21 | public ngOnInit(): void {
22 | this._renderer.addClass(this._ngEl.nativeElement, 'grid-placeholder');
23 | if (this._ngGrid.autoStyle) this._renderer.setStyle(this._ngEl.nativeElement, 'position', 'absolute');
24 | }
25 |
26 | public setSize(newSize: NgGridItemSize): void {
27 | this._size = newSize;
28 | this._recalculateDimensions();
29 | }
30 |
31 | public setGridPosition(newPosition: NgGridItemPosition): void {
32 | this._position = newPosition;
33 | this._recalculatePosition();
34 | }
35 |
36 | public setCascadeMode(cascade: string): void {
37 | this._cascadeMode = cascade;
38 | switch (cascade) {
39 | case 'up':
40 | case 'left':
41 | default:
42 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', '0px');
43 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', '0px');
44 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', null);
45 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', null);
46 | break;
47 | case 'right':
48 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', '0px');
49 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', '0px');
50 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', null);
51 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', null);
52 | break;
53 | case 'down':
54 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', '0px');
55 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', '0px');
56 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', null);
57 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', null);
58 | break;
59 | }
60 | }
61 |
62 | // Private methods
63 | private _setDimensions(w: number, h: number): void {
64 | this._renderer.setStyle(this._ngEl.nativeElement, 'width', w + 'px');
65 | this._renderer.setStyle(this._ngEl.nativeElement, 'height', h + 'px');
66 | }
67 |
68 | private _setPosition(x: number, y: number): void {
69 | switch (this._cascadeMode) {
70 | case 'up':
71 | case 'left':
72 | default:
73 | this._renderer.setStyle(this._ngEl.nativeElement, 'transform', 'translate(' + x + 'px, ' + y + 'px)');
74 | break;
75 | case 'right':
76 | this._renderer.setStyle(this._ngEl.nativeElement, 'transform', 'translate(' + -x + 'px, ' + y + 'px)');
77 | break;
78 | case 'down':
79 | this._renderer.setStyle(this._ngEl.nativeElement, 'transform', 'translate(' + x + 'px, ' + -y + 'px)');
80 | break;
81 | }
82 | }
83 |
84 | private _recalculatePosition(): void {
85 | const x: number = (this._ngGrid.colWidth + this._ngGrid.marginLeft + this._ngGrid.marginRight) * (this._position.col - 1) + this._ngGrid.marginLeft + this._ngGrid.screenMargin;
86 | const y: number = (this._ngGrid.rowHeight + this._ngGrid.marginTop + this._ngGrid.marginBottom) * (this._position.row - 1) + this._ngGrid.marginTop;
87 | this._setPosition(x, y);
88 | }
89 |
90 | private _recalculateDimensions(): void {
91 | const w: number = (this._ngGrid.colWidth * this._size.x) + ((this._ngGrid.marginLeft + this._ngGrid.marginRight) * (this._size.x - 1));
92 | const h: number = (this._ngGrid.rowHeight * this._size.y) + ((this._ngGrid.marginTop + this._ngGrid.marginBottom) * (this._size.y - 1));
93 | this._setDimensions(w, h);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/directives/NgGrid.ts:
--------------------------------------------------------------------------------
1 | import { Component, Directive, ElementRef, Renderer2, EventEmitter, ComponentFactoryResolver, Host, ViewEncapsulation, Type, ComponentRef, KeyValueDiffer, KeyValueDiffers, OnInit, OnDestroy, DoCheck, ViewContainerRef, Output } from '@angular/core';
2 | import { NgGridConfig, NgGridItemEvent, NgGridItemPosition, NgGridItemSize, NgGridRawPosition, NgGridItemDimensions, NgConfigFixDirection } from '../interfaces/INgGrid';
3 | import { NgGridItem } from './NgGridItem';
4 | import * as NgGridHelper from '../helpers/NgGridHelpers';
5 | import { NgGridPlaceholder } from '../components/NgGridPlaceholder';
6 | import { Subscription, Observable, fromEvent } from 'rxjs';
7 |
8 | @Directive({
9 | selector: '[ngGrid]',
10 | inputs: ['config: ngGrid'],
11 | host: {
12 | '(window:resize)': 'resizeEventHandler($event)',
13 | }
14 | })
15 | export class NgGrid implements OnInit, DoCheck, OnDestroy {
16 | public static CONST_DEFAULT_RESIZE_DIRECTIONS: string[] = [
17 | 'bottomright',
18 | 'bottomleft',
19 | 'topright',
20 | 'topleft',
21 | 'right',
22 | 'left',
23 | 'bottom',
24 | 'top',
25 | ];
26 |
27 | // Event Emitters
28 | @Output() public onDragStart: EventEmitter = new EventEmitter();
29 | @Output() public onDrag: EventEmitter = new EventEmitter();
30 | @Output() public onDragStop: EventEmitter = new EventEmitter();
31 | @Output() public onResizeStart: EventEmitter = new EventEmitter();
32 | @Output() public onResize: EventEmitter = new EventEmitter();
33 | @Output() public onResizeStop: EventEmitter = new EventEmitter();
34 | @Output() public onItemChange: EventEmitter> = new EventEmitter>();
35 |
36 | // Public variables
37 | public colWidth: number = 250;
38 | public rowHeight: number = 250;
39 | public minCols: number = 1;
40 | public minRows: number = 1;
41 | public marginTop: number = 10;
42 | public marginRight: number = 10;
43 | public marginBottom: number = 10;
44 | public marginLeft: number = 10;
45 | public screenMargin: number = 0;
46 | public isDragging: boolean = false;
47 | public isResizing: boolean = false;
48 | public autoStyle: boolean = true;
49 | public resizeEnable: boolean = true;
50 | public dragEnable: boolean = true;
51 | public cascade: string = 'up';
52 | public minWidth: number = 100;
53 | public minHeight: number = 100;
54 | public resizeDirections: string[] = NgGrid.CONST_DEFAULT_RESIZE_DIRECTIONS;
55 |
56 | // Private variables
57 | private _items: Map = new Map();
58 | private _draggingItem: NgGridItem = null;
59 | private _resizingItem: NgGridItem = null;
60 | private _resizeDirection: string = null;
61 | private _itemsInGrid: Set = new Set();
62 | private _containerWidth: number;
63 | private _containerHeight: number;
64 | private _maxCols: number = 0;
65 | private _maxRows: number = 0;
66 | private _visibleCols: number = 0;
67 | private _visibleRows: number = 0;
68 | private _setWidth: number = 250;
69 | private _setHeight: number = 250;
70 | private _posOffset: NgGridRawPosition = null;
71 | private _adding: boolean = false;
72 | private _placeholderRef: ComponentRef = null;
73 | private _fixToGrid: boolean = false;
74 | private _autoResize: boolean = false;
75 | private _differ: KeyValueDiffer;
76 | private _destroyed: boolean = false;
77 | private _maintainRatio: boolean = false;
78 | private _aspectRatio: number;
79 | private _preferNew: boolean = false;
80 | private _zoomOnDrag: boolean = false;
81 | private _limitToScreen: boolean = false;
82 | private _centerToScreen: boolean = false;
83 | private _curMaxRow: number = 0;
84 | private _curMaxCol: number = 0;
85 | private _dragReady: boolean = false;
86 | private _resizeReady: boolean = false;
87 | private _elementBasedDynamicRowHeight: boolean = false;
88 | private _itemFixDirection: NgConfigFixDirection = 'cascade';
89 | private _collisionFixDirection: NgConfigFixDirection = 'cascade';
90 | private _allowOverlap: boolean = false;
91 | private _cascadePromise: Promise;
92 | private _lastZValue: number = 1;
93 |
94 | // Events
95 | private _documentMousemove$: Observable;
96 | private _documentMouseup$: Observable;
97 | private _mousedown$: Observable;
98 | private _mousemove$: Observable;
99 | private _mouseup$: Observable;
100 | private _touchstart$: Observable;
101 | private _touchmove$: Observable;
102 | private _touchend$: Observable;
103 | private _subscriptions: Subscription[] = [];
104 |
105 | private _enabledListener: boolean = false;
106 |
107 | // Default config
108 | private static CONST_DEFAULT_CONFIG: NgGridConfig = {
109 | margins: [10],
110 | draggable: true,
111 | resizable: true,
112 | max_cols: 0,
113 | max_rows: 0,
114 | visible_cols: 0,
115 | visible_rows: 0,
116 | col_width: 250,
117 | row_height: 250,
118 | cascade: 'up',
119 | min_width: 100,
120 | min_height: 100,
121 | fix_to_grid: false,
122 | auto_style: true,
123 | auto_resize: false,
124 | maintain_ratio: false,
125 | prefer_new: false,
126 | zoom_on_drag: false,
127 | limit_to_screen: false,
128 | center_to_screen: false,
129 | resize_directions: NgGrid.CONST_DEFAULT_RESIZE_DIRECTIONS,
130 | element_based_row_height: false,
131 | fix_item_position_direction: 'cascade',
132 | fix_collision_position_direction: 'cascade',
133 | allow_overlap: false,
134 | };
135 | private _config = NgGrid.CONST_DEFAULT_CONFIG;
136 |
137 | // [ng-grid] attribute handler
138 | set config(v: NgGridConfig) {
139 | if (v == null || typeof v !== 'object') {
140 | return;
141 | }
142 |
143 | this.setConfig(v);
144 |
145 | if (this._differ == null && v != null) {
146 | this._differ = this._differs.find(this._config).create();
147 | }
148 |
149 | this._differ.diff(this._config);
150 | }
151 |
152 | // Constructor
153 | constructor(
154 | private _differs: KeyValueDiffers,
155 | private _ngEl: ElementRef,
156 | private _renderer: Renderer2,
157 | private componentFactoryResolver: ComponentFactoryResolver,
158 | ) {
159 | this._defineListeners();
160 | }
161 |
162 | // Public methods
163 | public ngOnInit(): void {
164 | this._renderer.addClass(this._ngEl.nativeElement, 'grid');
165 | if (this.autoStyle) this._renderer.setStyle(this._ngEl.nativeElement, 'position', 'relative');
166 | this.setConfig(this._config);
167 | }
168 |
169 | public ngOnDestroy(): void {
170 | this._destroyed = true;
171 | this._disableListeners();
172 | }
173 |
174 | public generateItemUid(): string {
175 | const uid: string = NgGridHelper.generateUuid();
176 |
177 | if (this._items.has(uid)) {
178 | return this.generateItemUid();
179 | }
180 |
181 | return uid;
182 | }
183 |
184 | public setConfig(config: NgGridConfig): void {
185 | this._config = config;
186 |
187 | var maxColRowChanged = false;
188 | for (var x in config) {
189 | var val = config[x];
190 | var intVal = !val ? 0 : parseInt(val);
191 |
192 | switch (x) {
193 | case 'margins':
194 | this.setMargins(val);
195 | break;
196 | case 'col_width':
197 | this.colWidth = Math.max(intVal, 1);
198 | break;
199 | case 'row_height':
200 | this.rowHeight = Math.max(intVal, 1);
201 | break;
202 | case 'auto_style':
203 | this.autoStyle = val ? true : false;
204 | break;
205 | case 'auto_resize':
206 | this._autoResize = val ? true : false;
207 | break;
208 | case 'draggable':
209 | this.dragEnable = val ? true : false;
210 | break;
211 | case 'resizable':
212 | this.resizeEnable = val ? true : false;
213 | break;
214 | case 'max_rows':
215 | maxColRowChanged = maxColRowChanged || this._maxRows != intVal;
216 | this._maxRows = intVal < 0 ? 0 : intVal;
217 | break;
218 | case 'max_cols':
219 | maxColRowChanged = maxColRowChanged || this._maxCols != intVal;
220 | this._maxCols = intVal < 0 ? 0 : intVal;
221 | break;
222 | case 'visible_rows':
223 | this._visibleRows = Math.max(intVal, 0);
224 | break;
225 | case 'visible_cols':
226 | this._visibleCols = Math.max(intVal, 0);
227 | break;
228 | case 'min_rows':
229 | this.minRows = Math.max(intVal, 1);
230 | break;
231 | case 'min_cols':
232 | this.minCols = Math.max(intVal, 1);
233 | break;
234 | case 'min_height':
235 | this.minHeight = Math.max(intVal, 1);
236 | break;
237 | case 'min_width':
238 | this.minWidth = Math.max(intVal, 1);
239 | break;
240 | case 'zoom_on_drag':
241 | this._zoomOnDrag = val ? true : false;
242 | break;
243 | case 'cascade':
244 | if (this.cascade != val) {
245 | this.cascade = val;
246 | this._cascadeGrid();
247 | }
248 | break;
249 | case 'fix_to_grid':
250 | this._fixToGrid = val ? true : false;
251 | break;
252 | case 'maintain_ratio':
253 | this._maintainRatio = val ? true : false;
254 | break;
255 | case 'prefer_new':
256 | this._preferNew = val ? true : false;
257 | break;
258 | case 'limit_to_screen':
259 | this._limitToScreen = !this._autoResize && !!val;
260 | break;
261 | case 'center_to_screen':
262 | this._centerToScreen = val ? true : false;
263 | break;
264 | case 'resize_directions':
265 | this.resizeDirections = val || ['bottomright', 'bottomleft', 'topright', 'topleft', 'right', 'left', 'bottom', 'top'];
266 | break;
267 | case 'element_based_row_height':
268 | this._elementBasedDynamicRowHeight = !!val;
269 | break;
270 | case 'fix_item_position_direction':
271 | this._itemFixDirection = val;
272 | break;
273 | case 'fix_collision_position_direction':
274 | this._collisionFixDirection = val;
275 | break;
276 | case 'allow_overlap':
277 | this._allowOverlap = !!val;
278 | break;
279 | }
280 | }
281 |
282 | if (this._allowOverlap && this.cascade !== 'off' && this.cascade !== '') {
283 | console.warn('Unable to overlap items when a cascade direction is set.');
284 | this._allowOverlap = false;
285 | }
286 |
287 | if (this.dragEnable || this.resizeEnable) {
288 | this._enableListeners();
289 | } else {
290 | this._disableListeners();
291 | }
292 |
293 | if (this._itemFixDirection === 'cascade') {
294 | this._itemFixDirection = this._getFixDirectionFromCascade();
295 | }
296 |
297 | if (this._collisionFixDirection === 'cascade') {
298 | this._collisionFixDirection = this._getFixDirectionFromCascade();
299 | }
300 |
301 | if (this._limitToScreen) {
302 | const newMaxCols = this._getContainerColumns();
303 |
304 | if (this._maxCols != newMaxCols) {
305 | this._maxCols = newMaxCols;
306 | maxColRowChanged = true;
307 | }
308 | }
309 |
310 | if (this._limitToScreen && this._centerToScreen) {
311 | this.screenMargin = this._getScreenMargin();
312 | } else {
313 | this.screenMargin = 0;
314 | }
315 |
316 | if (this._maintainRatio) {
317 | if (this.colWidth && this.rowHeight) {
318 | this._aspectRatio = this.colWidth / this.rowHeight;
319 | } else {
320 | this._maintainRatio = false;
321 | }
322 | }
323 |
324 | if (maxColRowChanged) {
325 | if (this._maxCols > 0 && this._maxRows > 0) { // Can't have both, prioritise on cascade
326 | switch (this.cascade) {
327 | case 'left':
328 | case 'right':
329 | this._maxCols = 0;
330 | break;
331 | case 'up':
332 | case 'down':
333 | default:
334 | this._maxRows = 0;
335 | break;
336 | }
337 | }
338 |
339 | this._updatePositionsAfterMaxChange();
340 | }
341 |
342 | this._calculateColWidth();
343 | this._calculateRowHeight();
344 |
345 | var maxWidth = this._maxCols * this.colWidth;
346 | var maxHeight = this._maxRows * this.rowHeight;
347 |
348 | if (maxWidth > 0 && this.minWidth > maxWidth) this.minWidth = 0.75 * this.colWidth;
349 | if (maxHeight > 0 && this.minHeight > maxHeight) this.minHeight = 0.75 * this.rowHeight;
350 |
351 | if (this.minWidth > this.colWidth) this.minCols = Math.max(this.minCols, Math.ceil(this.minWidth / this.colWidth));
352 | if (this.minHeight > this.rowHeight) this.minRows = Math.max(this.minRows, Math.ceil(this.minHeight / this.rowHeight));
353 |
354 | if (this._maxCols > 0 && this.minCols > this._maxCols) this.minCols = 1;
355 | if (this._maxRows > 0 && this.minRows > this._maxRows) this.minRows = 1;
356 |
357 | this._updateRatio();
358 |
359 | this._items.forEach((item: NgGridItem) => {
360 | this._removeFromGrid(item);
361 | item.setCascadeMode(this.cascade);
362 | });
363 |
364 | this._items.forEach((item: NgGridItem) => {
365 | item.recalculateSelf();
366 | this._addToGrid(item);
367 | });
368 |
369 | this._cascadeGrid();
370 | this._updateSize();
371 | }
372 |
373 | public getItemPosition(itemId: string): NgGridItemPosition {
374 | return this._items.has(itemId) ? this._items.get(itemId).getGridPosition() : null;
375 | }
376 |
377 | public getItemSize(itemId: string): NgGridItemSize {
378 | return this._items.has(itemId) ? this._items.get(itemId).getSize() : null;
379 | }
380 |
381 | public ngDoCheck(): boolean {
382 | if (this._differ != null) {
383 | var changes = this._differ.diff(this._config);
384 |
385 | if (changes != null) {
386 | this._applyChanges(changes);
387 |
388 | return true;
389 | }
390 | }
391 |
392 | return false;
393 | }
394 |
395 | public setMargins(margins: Array): void {
396 | this.marginTop = Math.max(parseInt(margins[0]), 0);
397 | this.marginRight = margins.length >= 2 ? Math.max(parseInt(margins[1]), 0) : this.marginTop;
398 | this.marginBottom = margins.length >= 3 ? Math.max(parseInt(margins[2]), 0) : this.marginTop;
399 | this.marginLeft = margins.length >= 4 ? Math.max(parseInt(margins[3]), 0) : this.marginRight;
400 | }
401 |
402 | public enableDrag(): void {
403 | this.dragEnable = true;
404 | }
405 |
406 | public disableDrag(): void {
407 | this.dragEnable = false;
408 | }
409 |
410 | public enableResize(): void {
411 | this.resizeEnable = true;
412 | }
413 |
414 | public disableResize(): void {
415 | this.resizeEnable = false;
416 | }
417 |
418 | public addItem(ngItem: NgGridItem): void {
419 | ngItem.setCascadeMode(this.cascade);
420 |
421 | if (!this._preferNew) {
422 | var newPos = this._fixGridPosition(ngItem.getGridPosition(), ngItem.getSize());
423 | ngItem.setGridPosition(newPos);
424 | }
425 |
426 | if (ngItem.uid === null || this._items.has(ngItem.uid)) {
427 | ngItem.uid = this.generateItemUid();
428 | }
429 |
430 | this._items.set(ngItem.uid, ngItem);
431 | this._addToGrid(ngItem);
432 |
433 | this._updateSize();
434 |
435 | this.triggerCascade().then(() => {
436 | ngItem.recalculateSelf();
437 | ngItem.onCascadeEvent();
438 |
439 | this._emitOnItemChange();
440 | });
441 |
442 | }
443 |
444 | public removeItem(ngItem: NgGridItem): void {
445 | this._removeFromGrid(ngItem);
446 |
447 | this._items.delete(ngItem.uid);
448 |
449 | if (this._destroyed) return;
450 |
451 | this.triggerCascade().then(() => {
452 | this._updateSize();
453 | this._items.forEach((item: NgGridItem) => item.recalculateSelf());
454 | this._emitOnItemChange();
455 | });
456 | }
457 |
458 | public updateItem(ngItem: NgGridItem): void {
459 | this._removeFromGrid(ngItem);
460 | this._addToGrid(ngItem);
461 |
462 | this.triggerCascade().then(() => {
463 | this._updateSize();
464 | ngItem.onCascadeEvent();
465 | });
466 | }
467 |
468 | public triggerCascade(): Promise {
469 | if (!this._cascadePromise) {
470 | this._cascadePromise = new Promise((resolve: () => void) => {
471 | setTimeout(() => {
472 | this._cascadePromise = null;
473 | this._cascadeGrid(null, null);
474 | resolve();
475 | }, 0);
476 | });
477 | }
478 |
479 | return this._cascadePromise;
480 | }
481 |
482 | public triggerResize(): void {
483 | this.resizeEventHandler(null);
484 | }
485 |
486 | public resizeEventHandler(e: any): void {
487 | this._calculateColWidth();
488 | this._calculateRowHeight();
489 |
490 | this._updateRatio();
491 |
492 | if (this._limitToScreen) {
493 | const newMaxColumns = this._getContainerColumns();
494 | if (this._maxCols !== newMaxColumns) {
495 | this._maxCols = newMaxColumns;
496 | this._updatePositionsAfterMaxChange();
497 | this._cascadeGrid();
498 | }
499 |
500 | if (this._centerToScreen) {
501 | this.screenMargin = this._getScreenMargin();
502 |
503 | this._items.forEach((item: NgGridItem) => {
504 | item.recalculateSelf();
505 | });
506 | }
507 | } else if (this._autoResize) {
508 | this._items.forEach((item: NgGridItem) => {
509 | item.recalculateSelf();
510 | });
511 | }
512 |
513 | this._updateSize();
514 | }
515 |
516 | public mouseDownEventHandler(e: MouseEvent | TouchEvent): void {
517 | var mousePos = this._getMousePosition(e);
518 | var item = this._getItemFromPosition(mousePos);
519 |
520 | if (item == null) return;
521 |
522 | const resizeDirection: string = item.canResize(e);
523 |
524 | if (this.resizeEnable && resizeDirection) {
525 | this._resizeReady = true;
526 | this._resizingItem = item;
527 | this._resizeDirection = resizeDirection;
528 |
529 | e.preventDefault();
530 | } else if (this.dragEnable && item.canDrag(e)) {
531 | this._dragReady = true;
532 | this._draggingItem = item;
533 |
534 | const itemPos = item.getPosition();
535 | this._posOffset = { 'left': (mousePos.left - itemPos.left), 'top': (mousePos.top - itemPos.top) }
536 |
537 | e.preventDefault();
538 | }
539 | }
540 |
541 | public mouseUpEventHandler(e: MouseEvent | TouchEvent): void {
542 | if (this.isDragging) {
543 | this._dragStop(e);
544 | } else if (this.isResizing) {
545 | this._resizeStop(e);
546 | } else if (this._dragReady || this._resizeReady) {
547 | this._cleanDrag();
548 | this._cleanResize();
549 | }
550 | }
551 |
552 | public mouseMoveEventHandler(e: MouseEvent | TouchEvent): void {
553 | if (this._resizeReady) {
554 | this._resizeStart(e);
555 | e.preventDefault();
556 | return;
557 | } else if (this._dragReady) {
558 | this._dragStart(e);
559 | e.preventDefault();
560 | return;
561 | }
562 |
563 | if (this.isDragging) {
564 | this._drag(e);
565 | } else if (this.isResizing) {
566 | this._resize(e);
567 | } else {
568 | var mousePos = this._getMousePosition(e);
569 | var item = this._getItemFromPosition(mousePos);
570 |
571 | if (item) {
572 | item.onMouseMove(e);
573 | }
574 | }
575 | }
576 |
577 | // Private methods
578 | private _getFixDirectionFromCascade(): NgConfigFixDirection {
579 | switch (this.cascade) {
580 | case 'up':
581 | case 'down':
582 | default:
583 | return 'vertical';
584 | case 'left':
585 | case 'right':
586 | return 'horizontal';
587 | }
588 | }
589 | private _updatePositionsAfterMaxChange(): void {
590 | this._items.forEach((item: NgGridItem) => {
591 | var pos = item.getGridPosition();
592 | var dims = item.getSize();
593 |
594 | if (!this._hasGridCollision(pos, dims) && this._isWithinBounds(pos, dims) && dims.x <= this._maxCols && dims.y <= this._maxRows) {
595 | return;
596 | }
597 |
598 | this._removeFromGrid(item);
599 |
600 | if (this._maxCols > 0 && dims.x > this._maxCols) {
601 | dims.x = this._maxCols;
602 | item.setSize(dims);
603 | } else if (this._maxRows > 0 && dims.y > this._maxRows) {
604 | dims.y = this._maxRows;
605 | item.setSize(dims);
606 | }
607 |
608 | if (this._hasGridCollision(pos, dims) || !this._isWithinBounds(pos, dims, true)) {
609 | var newPosition = this._fixGridPosition(pos, dims);
610 | item.setGridPosition(newPosition);
611 | }
612 |
613 | this._addToGrid(item);
614 | });
615 | }
616 |
617 | private _calculateColWidth(): void {
618 | if (this._autoResize) {
619 | if (this._maxCols > 0 || this._visibleCols > 0) {
620 | var maxCols = this._maxCols > 0 ? this._maxCols : this._visibleCols;
621 | var maxWidth: number = this._ngEl.nativeElement.getBoundingClientRect().width;
622 |
623 | var colWidth: number = Math.floor(maxWidth / maxCols);
624 | colWidth -= (this.marginLeft + this.marginRight);
625 | if (colWidth > 0) this.colWidth = colWidth;
626 |
627 | }
628 | }
629 |
630 | if (this.colWidth < this.minWidth || this.minCols > this._config.min_cols) {
631 | this.minCols = Math.max(this._config.min_cols, Math.ceil(this.minWidth / this.colWidth));
632 | }
633 | }
634 |
635 | private _calculateRowHeight(): void {
636 | if (this._autoResize) {
637 | if (this._maxRows > 0 || this._visibleRows > 0) {
638 | var maxRows = this._maxRows > 0 ? this._maxRows : this._visibleRows;
639 | let maxHeight: number;
640 |
641 | if (this._elementBasedDynamicRowHeight) {
642 | maxHeight = this._ngEl.nativeElement.getBoundingClientRect().height;
643 | } else {
644 | maxHeight = window.innerHeight - this.marginTop - this.marginBottom;
645 | }
646 |
647 | var rowHeight: number = Math.max(Math.floor(maxHeight / maxRows), this.minHeight);
648 | rowHeight -= (this.marginTop + this.marginBottom);
649 | if (rowHeight > 0) this.rowHeight = rowHeight;
650 |
651 | }
652 | }
653 |
654 | if (this.rowHeight < this.minHeight || this.minRows > this._config.min_rows) {
655 | this.minRows = Math.max(this._config.min_rows, Math.ceil(this.minHeight / this.rowHeight));
656 | }
657 | }
658 |
659 | private _updateRatio(): void {
660 | if (!this._autoResize || !this._maintainRatio) return;
661 |
662 | if (this._maxCols > 0 && this._visibleRows <= 0) {
663 | this.rowHeight = this.colWidth / this._aspectRatio;
664 | } else if (this._maxRows > 0 && this._visibleCols <= 0) {
665 | this.colWidth = this._aspectRatio * this.rowHeight;
666 | } else if (this._maxCols == 0 && this._maxRows == 0) {
667 | if (this._visibleCols > 0) {
668 | this.rowHeight = this.colWidth / this._aspectRatio;
669 | } else if (this._visibleRows > 0) {
670 | this.colWidth = this._aspectRatio * this.rowHeight;
671 | }
672 | }
673 | }
674 |
675 | private _applyChanges(changes: any): void {
676 | changes.forEachAddedItem((record: any) => { this._config[record.key] = record.currentValue; });
677 | changes.forEachChangedItem((record: any) => { this._config[record.key] = record.currentValue; });
678 | changes.forEachRemovedItem((record: any) => { delete this._config[record.key]; });
679 |
680 | this.setConfig(this._config);
681 | }
682 |
683 | private _resizeStart(e: any): void {
684 | if (!this.resizeEnable || !this._resizingItem) return;
685 |
686 | // Setup
687 | this._resizingItem.startMoving();
688 | this._removeFromGrid(this._resizingItem);
689 | this._createPlaceholder(this._resizingItem);
690 |
691 | if (this._allowOverlap) {
692 | this._resizingItem.zIndex = this._lastZValue++;
693 | }
694 |
695 | // Status Flags
696 | this.isResizing = true;
697 | this._resizeReady = false;
698 |
699 | // Events
700 | this.onResizeStart.emit(this._resizingItem);
701 | this._resizingItem.onResizeStartEvent();
702 | }
703 |
704 | private _dragStart(e: any): void {
705 | if (!this.dragEnable || !this._draggingItem) return;
706 |
707 | // Start dragging
708 | this._draggingItem.startMoving();
709 | this._removeFromGrid(this._draggingItem);
710 | this._createPlaceholder(this._draggingItem);
711 |
712 | if (this._allowOverlap) {
713 | this._draggingItem.zIndex = this._lastZValue++;
714 | }
715 |
716 | // Status Flags
717 | this.isDragging = true;
718 | this._dragReady = false;
719 |
720 | // Events
721 | this.onDragStart.emit(this._draggingItem);
722 | this._draggingItem.onDragStartEvent();
723 |
724 | // Zoom
725 | if (this._zoomOnDrag) {
726 | this._zoomOut();
727 | }
728 | }
729 |
730 | private _zoomOut(): void {
731 | this._renderer.setStyle(this._ngEl.nativeElement, 'transform', 'scale(0.5, 0.5)');
732 | }
733 |
734 | private _resetZoom(): void {
735 | this._renderer.setStyle(this._ngEl.nativeElement, 'transform', '');
736 | }
737 |
738 | private _drag(e: any): void {
739 | if (!this.isDragging) return;
740 |
741 | if (window.getSelection) {
742 | if (window.getSelection().empty) {
743 | window.getSelection().empty();
744 | } else if (window.getSelection().removeAllRanges) {
745 | window.getSelection().removeAllRanges();
746 | }
747 | } else if ((document).selection) {
748 | (document).selection.empty();
749 | }
750 |
751 | var mousePos = this._getMousePosition(e);
752 | var newL = (mousePos.left - this._posOffset.left);
753 | var newT = (mousePos.top - this._posOffset.top);
754 |
755 | var itemPos = this._draggingItem.getGridPosition();
756 | var gridPos = this._calculateGridPosition(newL, newT);
757 | var dims = this._draggingItem.getSize();
758 |
759 | gridPos = this._fixPosToBoundsX(gridPos, dims);
760 |
761 | if (!this._isWithinBoundsY(gridPos, dims)) {
762 | gridPos = this._fixPosToBoundsY(gridPos, dims);
763 | }
764 |
765 | if (gridPos.col != itemPos.col || gridPos.row != itemPos.row) {
766 | this._draggingItem.setGridPosition(gridPos, this._fixToGrid);
767 | this._placeholderRef.instance.setGridPosition(gridPos);
768 |
769 | if (['up', 'down', 'left', 'right'].indexOf(this.cascade) >= 0) {
770 | this._fixGridCollisions(gridPos, dims);
771 | this._cascadeGrid(gridPos, dims);
772 | }
773 | }
774 |
775 | if (!this._fixToGrid) {
776 | this._draggingItem.setPosition(newL, newT);
777 | }
778 |
779 | this.onDrag.emit(this._draggingItem);
780 | this._draggingItem.onDragEvent();
781 | }
782 |
783 | private _resize(e: any): void {
784 | if (!this.isResizing) { return; }
785 |
786 | if (window.getSelection) {
787 | if (window.getSelection().empty) {
788 | window.getSelection().empty();
789 | } else if (window.getSelection().removeAllRanges) {
790 | window.getSelection().removeAllRanges();
791 | }
792 | } else if ((document).selection) {
793 | (document).selection.empty();
794 | }
795 |
796 | const mousePos = this._getMousePosition(e);
797 | const itemPos = this._resizingItem.getPosition();
798 | const itemDims = this._resizingItem.getDimensions();
799 | const endCorner = {
800 | left: itemPos.left + itemDims.width,
801 | top: itemPos.top + itemDims.height,
802 | }
803 |
804 | const resizeTop = this._resizeDirection.includes('top');
805 | const resizeBottom = this._resizeDirection.includes('bottom');
806 | const resizeLeft = this._resizeDirection.includes('left')
807 | const resizeRight = this._resizeDirection.includes('right');
808 |
809 | // Calculate new width and height based upon resize direction
810 | let newW = resizeRight
811 | ? (mousePos.left - itemPos.left + 1)
812 | : resizeLeft
813 | ? (endCorner.left - mousePos.left + 1)
814 | : itemDims.width;
815 | let newH = resizeBottom
816 | ? (mousePos.top - itemPos.top + 1)
817 | : resizeTop
818 | ? (endCorner.top - mousePos.top + 1)
819 | : itemDims.height;
820 |
821 | if (newW < this.minWidth)
822 | newW = this.minWidth;
823 | if (newH < this.minHeight)
824 | newH = this.minHeight;
825 | if (newW < this._resizingItem.minWidth)
826 | newW = this._resizingItem.minWidth;
827 | if (newH < this._resizingItem.minHeight)
828 | newH = this._resizingItem.minHeight;
829 |
830 | let newX = itemPos.left;
831 | let newY = itemPos.top;
832 |
833 | if (resizeLeft)
834 | newX = endCorner.left - newW;
835 | if (resizeTop)
836 | newY = endCorner.top - newH;
837 |
838 | let calcSize = this._calculateGridSize(newW, newH);
839 | const itemSize = this._resizingItem.getSize();
840 | const iGridPos = this._resizingItem.getGridPosition();
841 | const bottomRightCorner = {
842 | col: iGridPos.col + itemSize.x,
843 | row: iGridPos.row + itemSize.y,
844 | };
845 | const targetPos: NgGridItemPosition = Object.assign({}, iGridPos);
846 |
847 | if (this._resizeDirection.includes('top'))
848 | targetPos.row = bottomRightCorner.row - calcSize.y;
849 | if (this._resizeDirection.includes('left'))
850 | targetPos.col = bottomRightCorner.col - calcSize.x;
851 |
852 | if (!this._isWithinBoundsX(targetPos, calcSize))
853 | calcSize = this._fixSizeToBoundsX(targetPos, calcSize);
854 |
855 | if (!this._isWithinBoundsY(targetPos, calcSize))
856 | calcSize = this._fixSizeToBoundsY(targetPos, calcSize);
857 |
858 | calcSize = this._resizingItem.fixResize(calcSize);
859 |
860 | if (calcSize.x != itemSize.x || calcSize.y != itemSize.y) {
861 | this._resizingItem.setGridPosition(targetPos, this._fixToGrid);
862 | this._placeholderRef.instance.setGridPosition(targetPos);
863 | this._resizingItem.setSize(calcSize, this._fixToGrid);
864 | this._placeholderRef.instance.setSize(calcSize);
865 |
866 | if (['up', 'down', 'left', 'right'].indexOf(this.cascade) >= 0) {
867 | this._fixGridCollisions(targetPos, calcSize);
868 | this._cascadeGrid(targetPos, calcSize);
869 | }
870 | }
871 |
872 | if (!this._fixToGrid) {
873 | this._resizingItem.setDimensions(newW, newH);
874 | this._resizingItem.setPosition(newX, newY);
875 | }
876 |
877 | this.onResize.emit(this._resizingItem);
878 | this._resizingItem.onResizeEvent();
879 | }
880 |
881 | private _dragStop(e: any): void {
882 | if (!this.isDragging) return;
883 |
884 | this.isDragging = false;
885 |
886 | var itemPos = this._draggingItem.getGridPosition();
887 |
888 | this._draggingItem.setGridPosition(itemPos);
889 | this._addToGrid(this._draggingItem);
890 |
891 | this._cascadeGrid();
892 | this._updateSize();
893 |
894 | this._draggingItem.stopMoving();
895 | this._draggingItem.onDragStopEvent();
896 | this.onDragStop.emit(this._draggingItem);
897 |
898 | this._cleanDrag();
899 | this._placeholderRef.destroy();
900 |
901 | this._emitOnItemChange();
902 |
903 | if (this._zoomOnDrag) {
904 | this._resetZoom();
905 | }
906 | }
907 |
908 | private _resizeStop(e: any): void {
909 | if (!this.isResizing) return;
910 |
911 | this.isResizing = false;
912 |
913 | const itemDims = this._resizingItem.getSize();
914 | this._resizingItem.setSize(itemDims);
915 |
916 | const itemPos = this._resizingItem.getGridPosition();
917 | this._resizingItem.setGridPosition(itemPos);
918 |
919 | this._addToGrid(this._resizingItem);
920 |
921 | this._cascadeGrid();
922 | this._updateSize();
923 |
924 | this._resizingItem.stopMoving();
925 | this._resizingItem.onResizeStopEvent();
926 | this.onResizeStop.emit(this._resizingItem);
927 |
928 | this._cleanResize();
929 | this._placeholderRef.destroy();
930 |
931 | this._emitOnItemChange();
932 | }
933 |
934 | private _cleanDrag(): void {
935 | this._draggingItem = null;
936 | this._posOffset = null;
937 | this.isDragging = false;
938 | this._dragReady = false;
939 | }
940 |
941 | private _cleanResize(): void {
942 | this._resizingItem = null;
943 | this._resizeDirection = null;
944 | this.isResizing = false;
945 | this._resizeReady = false;
946 | }
947 |
948 | private _calculateGridSize(width: number, height: number): NgGridItemSize {
949 | width += this.marginLeft + this.marginRight;
950 | height += this.marginTop + this.marginBottom;
951 |
952 | var sizex = Math.max(this.minCols, Math.round(width / (this.colWidth + this.marginLeft + this.marginRight)));
953 | var sizey = Math.max(this.minRows, Math.round(height / (this.rowHeight + this.marginTop + this.marginBottom)));
954 |
955 | if (!this._isWithinBoundsX({ col: 1, row: 1 }, { x: sizex, y: sizey })) sizex = this._maxCols;
956 | if (!this._isWithinBoundsY({ col: 1, row: 1 }, { x: sizex, y: sizey })) sizey = this._maxRows;
957 |
958 | return { 'x': sizex, 'y': sizey };
959 | }
960 |
961 | private _calculateGridPosition(left: number, top: number): NgGridItemPosition {
962 | var col = Math.max(1, Math.round(left / (this.colWidth + this.marginLeft + this.marginRight)) + 1);
963 | var row = Math.max(1, Math.round(top / (this.rowHeight + this.marginTop + this.marginBottom)) + 1);
964 |
965 | if (!this._isWithinBoundsX({ col: col, row: row }, { x: 1, y: 1 })) col = this._maxCols;
966 | if (!this._isWithinBoundsY({ col: col, row: row }, { x: 1, y: 1 })) row = this._maxRows;
967 |
968 | return { 'col': col, 'row': row };
969 | }
970 |
971 | private _hasGridCollision(pos: NgGridItemPosition, dims: NgGridItemSize): boolean {
972 | var positions = this._getCollisions(pos, dims);
973 |
974 | if (positions == null || positions.length == 0) return false;
975 |
976 | return positions.some((v: NgGridItem) => {
977 | return !(v === null);
978 | });
979 | }
980 |
981 | private _getCollisions(pos: NgGridItemPosition, dims: NgGridItemSize): Array {
982 | if (this._allowOverlap) return [];
983 |
984 | const returns: Array = [];
985 |
986 | if (!pos.col) { pos.col = 1; }
987 | if (!pos.row) { pos.row = 1; }
988 |
989 | const leftCol = pos.col;
990 | const rightCol = pos.col + dims.x;
991 | const topRow = pos.row;
992 | const bottomRow = pos.row + dims.y;
993 |
994 | this._itemsInGrid.forEach((itemId: string) => {
995 | const item: NgGridItem = this._items.get(itemId);
996 |
997 | if (!item) {
998 | this._itemsInGrid.delete(itemId);
999 | return;
1000 | }
1001 |
1002 | const itemLeftCol = item.col;
1003 | const itemRightCol = item.col + item.sizex;
1004 | const itemTopRow = item.row;
1005 | const itemBottomRow = item.row + item.sizey;
1006 |
1007 | const withinColumns = leftCol < itemRightCol && itemLeftCol < rightCol;
1008 | const withinRows = topRow < itemBottomRow && itemTopRow < bottomRow;
1009 |
1010 | if (withinColumns && withinRows) {
1011 | returns.push(item);
1012 | }
1013 | });
1014 |
1015 | return returns;
1016 | }
1017 |
1018 | private _fixGridCollisions(pos: NgGridItemPosition, dims: NgGridItemSize): void {
1019 | const collisions: Array = this._getCollisions(pos, dims);
1020 | if (collisions.length === 0) { return; }
1021 |
1022 | for (let collision of collisions) {
1023 | this._removeFromGrid(collision);
1024 |
1025 | const itemDims: NgGridItemSize = collision.getSize();
1026 | const itemPos: NgGridItemPosition = collision.getGridPosition();
1027 | let newItemPos: NgGridItemPosition = { col: itemPos.col, row: itemPos.row };
1028 |
1029 | if (this._collisionFixDirection === 'vertical') {
1030 | newItemPos.row = pos.row + dims.y;
1031 |
1032 | if (!this._isWithinBoundsY(newItemPos, itemDims)) {
1033 | newItemPos.col = pos.col + dims.x;
1034 | newItemPos.row = 1;
1035 | }
1036 | } else if (this._collisionFixDirection === 'horizontal') {
1037 | newItemPos.col = pos.col + dims.x;
1038 |
1039 | if (!this._isWithinBoundsX(newItemPos, itemDims)) {
1040 | newItemPos.col = 1;
1041 | newItemPos.row = pos.row + dims.y;
1042 | }
1043 | }
1044 |
1045 | collision.setGridPosition(newItemPos);
1046 |
1047 | this._fixGridCollisions(newItemPos, itemDims);
1048 | this._addToGrid(collision);
1049 | collision.onCascadeEvent();
1050 | }
1051 |
1052 | this._fixGridCollisions(pos, dims);
1053 | }
1054 |
1055 | private _cascadeGrid(pos?: NgGridItemPosition, dims?: NgGridItemSize): void {
1056 | if (this._destroyed) return;
1057 | if (this._allowOverlap) return;
1058 | if (!pos !== !dims) throw new Error('Cannot cascade with only position and not dimensions');
1059 |
1060 | if (this.isDragging && this._draggingItem && !pos && !dims) {
1061 | pos = this._draggingItem.getGridPosition();
1062 | dims = this._draggingItem.getSize();
1063 | } else if (this.isResizing && this._resizingItem && !pos && !dims) {
1064 | pos = this._resizingItem.getGridPosition();
1065 | dims = this._resizingItem.getSize();
1066 | }
1067 |
1068 | let itemsInGrid: NgGridItem[] = Array.from(this._itemsInGrid, (itemId: string) => this._items.get(itemId));
1069 |
1070 | switch (this.cascade) {
1071 | case 'up':
1072 | case 'down':
1073 | itemsInGrid = itemsInGrid.sort(NgGridHelper.sortItemsByPositionVertical);
1074 | const lowestRowPerColumn: Map = new Map();
1075 |
1076 | for (let item of itemsInGrid) {
1077 | if (item.isFixed) continue;
1078 |
1079 | const itemDims: NgGridItemSize = item.getSize();
1080 | const itemPos: NgGridItemPosition = item.getGridPosition();
1081 |
1082 | let lowestRowForItem: number = lowestRowPerColumn.get(itemPos.col) || 1;
1083 |
1084 | for (let i: number = 1; i < itemDims.x; i++) {
1085 | const lowestRowForColumn = lowestRowPerColumn.get(itemPos.col + i) || 1;
1086 | lowestRowForItem = Math.max(lowestRowForColumn, lowestRowForItem);
1087 | }
1088 |
1089 | const leftCol = itemPos.col;
1090 | const rightCol = itemPos.col + itemDims.x;
1091 |
1092 | if (pos && dims) {
1093 | const withinColumns = rightCol > pos.col && leftCol < (pos.col + dims.x);
1094 |
1095 | if (withinColumns) { // If our element is in one of the item's columns
1096 | const roomAboveItem = itemDims.y <= (pos.row - lowestRowForItem);
1097 |
1098 | if (!roomAboveItem) { // Item can't fit above our element
1099 | lowestRowForItem = Math.max(lowestRowForItem, pos.row + dims.y); // Set the lowest row to be below it
1100 | }
1101 | }
1102 | }
1103 |
1104 | const newPos: NgGridItemPosition = { col: itemPos.col, row: lowestRowForItem };
1105 |
1106 | // What if it's not within bounds Y?
1107 | if (lowestRowForItem != itemPos.row && this._isWithinBoundsY(newPos, itemDims)) { // If the item is not already on this row move it up
1108 | this._removeFromGrid(item);
1109 |
1110 | item.setGridPosition(newPos);
1111 |
1112 | item.onCascadeEvent();
1113 | this._addToGrid(item);
1114 | }
1115 |
1116 | for (let i: number = 0; i < itemDims.x; i++) {
1117 | lowestRowPerColumn.set(itemPos.col + i, lowestRowForItem + itemDims.y); // Update the lowest row to be below the item
1118 | }
1119 | }
1120 | break;
1121 | case 'left':
1122 | case 'right':
1123 | itemsInGrid = itemsInGrid.sort(NgGridHelper.sortItemsByPositionHorizontal);
1124 | const lowestColumnPerRow: Map = new Map();
1125 |
1126 | for (let item of itemsInGrid) {
1127 | const itemDims: NgGridItemSize = item.getSize();
1128 | const itemPos: NgGridItemPosition = item.getGridPosition();
1129 |
1130 | let lowestColumnForItem: number = lowestColumnPerRow.get(itemPos.row) || 1;
1131 |
1132 | for (let i: number = 1; i < itemDims.y; i++) {
1133 | let lowestOffsetColumn: number = lowestColumnPerRow.get(itemPos.row + i) || 1;
1134 | lowestColumnForItem = Math.max(lowestOffsetColumn, lowestColumnForItem);
1135 | }
1136 |
1137 | const topRow = itemPos.row;
1138 | const bottomRow = itemPos.row + itemDims.y;
1139 |
1140 | if (pos && dims) {
1141 | const withinRows = bottomRow > pos.col && topRow < (pos.col + dims.x);
1142 |
1143 | if (withinRows) { // If our element is in one of the item's rows
1144 | const roomNextToItem = itemDims.x <= (pos.col - lowestColumnForItem);
1145 |
1146 | if (!roomNextToItem) { // Item can't fit next to our element
1147 | lowestColumnForItem = Math.max(lowestColumnForItem, pos.col + dims.x); // Set the lowest col to be the other side of it
1148 | }
1149 | }
1150 | }
1151 |
1152 | const newPos: NgGridItemPosition = { col: lowestColumnForItem, row: itemPos.row };
1153 |
1154 | if (lowestColumnForItem != itemPos.col && this._isWithinBoundsX(newPos, itemDims)) { // If the item is not already on this col move it up
1155 | this._removeFromGrid(item);
1156 |
1157 | item.setGridPosition(newPos);
1158 |
1159 | item.onCascadeEvent();
1160 | this._addToGrid(item);
1161 | }
1162 |
1163 | for (let i: number = 0; i < itemDims.y; i++) {
1164 | lowestColumnPerRow.set(itemPos.row + i, lowestColumnForItem + itemDims.x); // Update the lowest col to be below the item
1165 | }
1166 | }
1167 | break;
1168 | default:
1169 | break;
1170 | }
1171 | }
1172 |
1173 | private _fixGridPosition(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemPosition {
1174 | if (!this._hasGridCollision(pos, dims)) return pos;
1175 |
1176 | const maxRow = this._maxRows === 0 ? this._getMaxRow() : this._maxRows;
1177 | const maxCol = this._maxCols === 0 ? this._getMaxCol() : this._maxCols;
1178 | const newPos = {
1179 | col: pos.col,
1180 | row: pos.row,
1181 | };
1182 |
1183 | if (this._itemFixDirection === 'vertical') {
1184 | fixLoop:
1185 | for (; newPos.col <= maxRow;) {
1186 | const itemsInPath = this._getItemsInVerticalPath(newPos, dims, newPos.row);
1187 | let nextRow = newPos.row;
1188 |
1189 | for (let item of itemsInPath) {
1190 | if (item.row - nextRow >= dims.y) {
1191 | newPos.row = nextRow;
1192 | break fixLoop;
1193 | }
1194 |
1195 | nextRow = item.row + item.sizey;
1196 | }
1197 |
1198 | if (maxRow - nextRow >= dims.y) {
1199 | newPos.row = nextRow;
1200 | break fixLoop;
1201 | }
1202 |
1203 | newPos.col = Math.max(newPos.col + 1, Math.min.apply(Math, itemsInPath.map((item) => item.col + dims.x)));
1204 | newPos.row = 1;
1205 | }
1206 | } else if (this._itemFixDirection === 'horizontal') {
1207 | fixLoop:
1208 | for (; newPos.row <= maxRow;) {
1209 | const itemsInPath = this._getItemsInHorizontalPath(newPos, dims, newPos.col);
1210 | let nextCol = newPos.col;
1211 |
1212 | for (let item of itemsInPath) {
1213 | if (item.col - nextCol >= dims.x) {
1214 | newPos.col = nextCol;
1215 | break fixLoop;
1216 | }
1217 |
1218 | nextCol = item.col + item.sizex;
1219 | }
1220 |
1221 | if (maxCol - nextCol >= dims.x) {
1222 | newPos.col = nextCol;
1223 | break fixLoop;
1224 | }
1225 |
1226 | newPos.row = Math.max(newPos.row + 1, Math.min.apply(Math, itemsInPath.map((item) => item.row + dims.y)));
1227 | newPos.col = 1;
1228 | }
1229 | }
1230 |
1231 | return newPos;
1232 | }
1233 |
1234 | private _getItemsInHorizontalPath(pos: NgGridItemPosition, dims: NgGridItemSize, startColumn: number = 0): NgGridItem[] {
1235 | const itemsInPath: NgGridItem[] = [];
1236 | const topRow: number = pos.row + dims.y - 1;
1237 |
1238 | this._itemsInGrid.forEach((itemId: string) => {
1239 | const item = this._items.get(itemId);
1240 | if (item.col + item.sizex - 1 < startColumn) { return; } // Item falls after start column
1241 | if (item.row > topRow) { return; } // Item falls above path
1242 | if (item.row + item.sizey - 1 < pos.row) { return; } // Item falls below path
1243 | itemsInPath.push(item);
1244 | });
1245 |
1246 | return itemsInPath;
1247 | }
1248 |
1249 | private _getItemsInVerticalPath(pos: NgGridItemPosition, dims: NgGridItemSize, startRow: number = 0): NgGridItem[] {
1250 | const itemsInPath: NgGridItem[] = [];
1251 | const rightCol: number = pos.col + dims.x - 1;
1252 |
1253 | this._itemsInGrid.forEach((itemId: string) => {
1254 | const item = this._items.get(itemId);
1255 | if (item.row + item.sizey - 1 < startRow) { return; } // Item falls above start row
1256 | if (item.col > rightCol) { return; } // Item falls after path
1257 | if (item.col + item.sizex - 1 < pos.col) { return; } // Item falls before path
1258 | itemsInPath.push(item);
1259 | });
1260 |
1261 | return itemsInPath;
1262 | }
1263 |
1264 | private _isWithinBoundsX(pos: NgGridItemPosition, dims: NgGridItemSize, allowExcessiveItems: boolean = false) {
1265 | return this._maxCols == 0 || (allowExcessiveItems && pos.col == 1) || (pos.col + dims.x - 1) <= this._maxCols;
1266 | }
1267 |
1268 | private _fixPosToBoundsX(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemPosition {
1269 | if (!this._isWithinBoundsX(pos, dims)) {
1270 | pos.col = Math.max(this._maxCols - (dims.x - 1), 1);
1271 | pos.row ++;
1272 | }
1273 | return pos;
1274 | }
1275 |
1276 | private _fixSizeToBoundsX(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemSize {
1277 | if (!this._isWithinBoundsX(pos, dims)) {
1278 | dims.x = Math.max(this._maxCols - (pos.col - 1), 1);
1279 | dims.y++;
1280 | }
1281 | return dims;
1282 | }
1283 |
1284 | private _isWithinBoundsY(pos: NgGridItemPosition, dims: NgGridItemSize, allowExcessiveItems: boolean = false) {
1285 | return this._maxRows == 0 || (allowExcessiveItems && pos.row == 1) || (pos.row + dims.y - 1) <= this._maxRows;
1286 | }
1287 |
1288 | private _fixPosToBoundsY(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemPosition {
1289 | if (!this._isWithinBoundsY(pos, dims)) {
1290 | pos.row = Math.max(this._maxRows - (dims.y - 1), 1);
1291 | pos.col++;
1292 | }
1293 | return pos;
1294 | }
1295 |
1296 | private _fixSizeToBoundsY(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemSize {
1297 | if (!this._isWithinBoundsY(pos, dims)) {
1298 | dims.y = Math.max(this._maxRows - (pos.row - 1), 1);
1299 | dims.x++;
1300 | }
1301 | return dims;
1302 | }
1303 |
1304 | private _isWithinBounds(pos: NgGridItemPosition, dims: NgGridItemSize, allowExcessiveItems: boolean = false) {
1305 | return this._isWithinBoundsX(pos, dims, allowExcessiveItems) && this._isWithinBoundsY(pos, dims, allowExcessiveItems);
1306 | }
1307 |
1308 | private _fixPosToBounds(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemPosition {
1309 | return this._fixPosToBoundsX(this._fixPosToBoundsY(pos, dims), dims);
1310 | }
1311 |
1312 | private _fixSizeToBounds(pos: NgGridItemPosition, dims: NgGridItemSize): NgGridItemSize {
1313 | return this._fixSizeToBoundsX(pos, this._fixSizeToBoundsY(pos, dims));
1314 | }
1315 |
1316 | private _addToGrid(item: NgGridItem): void {
1317 | let pos: NgGridItemPosition = item.getGridPosition();
1318 | const dims: NgGridItemSize = item.getSize();
1319 |
1320 | if (this._hasGridCollision(pos, dims)) {
1321 | this._fixGridCollisions(pos, dims);
1322 | pos = item.getGridPosition();
1323 | }
1324 |
1325 | if (this._allowOverlap) {
1326 | item.zIndex = this._lastZValue++;
1327 | }
1328 |
1329 | this._itemsInGrid.add(item.uid);
1330 | }
1331 |
1332 | private _removeFromGrid(item: NgGridItem): void {
1333 | this._itemsInGrid.delete(item.uid);
1334 | }
1335 |
1336 | private _updateSize(): void {
1337 | if (this._destroyed) return;
1338 | let maxCol: number = this._getMaxCol();
1339 | let maxRow: number = this._getMaxRow();
1340 |
1341 | if (maxCol != this._curMaxCol || maxRow != this._curMaxRow) {
1342 | this._curMaxCol = maxCol;
1343 | this._curMaxRow = maxRow;
1344 | }
1345 |
1346 | this._renderer.setStyle(this._ngEl.nativeElement, 'width', '100%');//(maxCol * (this.colWidth + this.marginLeft + this.marginRight))+'px');
1347 | if (!this._elementBasedDynamicRowHeight) {
1348 | this._renderer.setStyle(this._ngEl.nativeElement, 'height', (maxRow * (this.rowHeight + this.marginTop + this.marginBottom)) + 'px');
1349 | }
1350 | }
1351 |
1352 | private _getMaxRow(): number {
1353 | const itemsRows: number[] = Array.from(this._itemsInGrid, (itemId: string) => {
1354 | const item = this._items.get(itemId);
1355 | if (!item) return 0;
1356 | return item.row + item.sizey - 1;
1357 | });
1358 |
1359 | return Math.max.apply(null, itemsRows);
1360 | }
1361 |
1362 | private _getMaxCol(): number {
1363 | const itemsCols: number[] = Array.from(this._itemsInGrid, (itemId: string) => {
1364 | const item = this._items.get(itemId);
1365 | if (!item) return 0;
1366 | return item.col + item.sizex - 1;
1367 | });
1368 |
1369 | return Math.max.apply(null, itemsCols);
1370 | }
1371 |
1372 | private _getMousePosition(e: any): NgGridRawPosition {
1373 | if (((window).TouchEvent && e instanceof TouchEvent) || (e.touches || e.changedTouches)) {
1374 | e = e.touches.length > 0 ? e.touches[0] : e.changedTouches[0];
1375 | }
1376 |
1377 | const refPos: any = this._ngEl.nativeElement.getBoundingClientRect();
1378 |
1379 | let left: number = e.clientX - refPos.left;
1380 | let top: number = e.clientY - refPos.top;
1381 |
1382 | if (this.cascade == 'down') top = refPos.top + refPos.height - e.clientY;
1383 | if (this.cascade == 'right') left = refPos.left + refPos.width - e.clientX;
1384 |
1385 | if (this.isDragging && this._zoomOnDrag) {
1386 | left *= 2;
1387 | top *= 2;
1388 | }
1389 |
1390 | return {
1391 | left: left,
1392 | top: top
1393 | };
1394 | }
1395 |
1396 | private _getAbsoluteMousePosition(e: any): NgGridRawPosition {
1397 | if (((window).TouchEvent && e instanceof TouchEvent) || (e.touches || e.changedTouches)) {
1398 | e = e.touches.length > 0 ? e.touches[0] : e.changedTouches[0];
1399 | }
1400 |
1401 | return {
1402 | left: e.clientX,
1403 | top: e.clientY
1404 | };
1405 | }
1406 |
1407 | private _getContainerColumns(): number {
1408 | const maxWidth: number = this._ngEl.nativeElement.getBoundingClientRect().width;
1409 | const itemWidth: number = this.colWidth + this.marginLeft + this.marginRight;
1410 | return Math.floor(maxWidth / itemWidth);
1411 | }
1412 |
1413 | private _getContainerRows(): number {
1414 | const maxHeight: number = window.innerHeight - this.marginTop - this.marginBottom;
1415 | return Math.floor(maxHeight / (this.rowHeight + this.marginTop + this.marginBottom));
1416 | }
1417 |
1418 | private _getScreenMargin(): number {
1419 | const maxWidth: number = this._ngEl.nativeElement.getBoundingClientRect().width;
1420 | const itemWidth: number = this.colWidth + this.marginLeft + this.marginRight;
1421 | return Math.floor((maxWidth - (this._maxCols * itemWidth)) / 2);;
1422 | }
1423 |
1424 | private _getItemFromPosition(position: NgGridRawPosition): NgGridItem {
1425 | return Array.from(this._itemsInGrid, (itemId: string) => this._items.get(itemId)).find((item: NgGridItem) => {
1426 | if (!item) return false;
1427 |
1428 | const size: NgGridItemDimensions = item.getDimensions();
1429 | const pos: NgGridRawPosition = item.getPosition();
1430 |
1431 | return position.left >= pos.left && position.left < (pos.left + size.width) &&
1432 | position.top >= pos.top && position.top < (pos.top + size.height);
1433 | });
1434 | }
1435 |
1436 | private _createPlaceholder(item: NgGridItem): void {
1437 | const pos: NgGridItemPosition = item.getGridPosition();
1438 | const dims: NgGridItemSize = item.getSize();
1439 |
1440 | const factory = this.componentFactoryResolver.resolveComponentFactory(NgGridPlaceholder);
1441 | var componentRef: ComponentRef = item.containerRef.createComponent(factory);
1442 | this._placeholderRef = componentRef;
1443 | const placeholder: NgGridPlaceholder = componentRef.instance;
1444 | placeholder.registerGrid(this);
1445 | placeholder.setCascadeMode(this.cascade);
1446 | placeholder.setGridPosition({ col: pos.col, row: pos.row });
1447 | placeholder.setSize({ x: dims.x, y: dims.y });
1448 | }
1449 |
1450 | private _emitOnItemChange() {
1451 | const itemOutput: any[] = Array.from(this._itemsInGrid)
1452 | .map((itemId: string) => this._items.get(itemId))
1453 | .filter((item: NgGridItem) => !!item)
1454 | .map((item: NgGridItem) => item.getEventOutput());
1455 |
1456 | this.onItemChange.emit(itemOutput);
1457 | }
1458 |
1459 | private _defineListeners(): void {
1460 | const element = this._ngEl.nativeElement;
1461 |
1462 | this._documentMousemove$ = fromEvent(document, 'mousemove');
1463 | this._documentMouseup$ = fromEvent(document, 'mouseup');
1464 | this._mousedown$ = fromEvent(element, 'mousedown');
1465 | this._mousemove$ = fromEvent(element, 'mousemove');
1466 | this._mouseup$ = fromEvent(element, 'mouseup');
1467 | this._touchstart$ = fromEvent(element, 'touchstart');
1468 | this._touchmove$ = fromEvent(element, 'touchmove');
1469 | this._touchend$ = fromEvent(element, 'touchend');
1470 | }
1471 |
1472 | private _enableListeners(): void {
1473 | if (this._enabledListener) {
1474 | return;
1475 | }
1476 |
1477 | this._enableMouseListeners();
1478 |
1479 | if (this._isTouchDevice()) {
1480 | this._enableTouchListeners();
1481 | }
1482 |
1483 | this._enabledListener = true;
1484 | }
1485 |
1486 | private _disableListeners(): void {
1487 | this._subscriptions.forEach((subs: Subscription) => subs.unsubscribe());
1488 | this._enabledListener = false;
1489 | }
1490 |
1491 | private _isTouchDevice(): boolean {
1492 | return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
1493 | };
1494 |
1495 | private _enableTouchListeners(): void {
1496 | const touchstartSubs = this._touchstart$.subscribe((e: TouchEvent) => this.mouseDownEventHandler(e));
1497 | const touchmoveSubs = this._touchmove$.subscribe((e: TouchEvent) => this.mouseMoveEventHandler(e));
1498 | const touchendSubs = this._touchend$.subscribe((e: TouchEvent) => this.mouseUpEventHandler(e));
1499 |
1500 | this._subscriptions.push(
1501 | touchstartSubs,
1502 | touchmoveSubs,
1503 | touchendSubs
1504 | );
1505 | }
1506 |
1507 | private _enableMouseListeners(): void {
1508 | const documentMousemoveSubs = this._documentMousemove$.subscribe((e: MouseEvent) => this.mouseMoveEventHandler(e));
1509 | const documentMouseupSubs = this._documentMouseup$.subscribe((e: MouseEvent) => this.mouseUpEventHandler(e));
1510 | const mousedownSubs = this._mousedown$.subscribe((e: MouseEvent) => this.mouseDownEventHandler(e));
1511 | const mousemoveSubs = this._mousemove$.subscribe((e: MouseEvent) => this.mouseMoveEventHandler(e));
1512 | const mouseupSubs = this._mouseup$.subscribe((e: MouseEvent) => this.mouseUpEventHandler(e));
1513 |
1514 | this._subscriptions.push(
1515 | documentMousemoveSubs,
1516 | documentMouseupSubs,
1517 | mousedownSubs,
1518 | mousemoveSubs,
1519 | mouseupSubs
1520 | );
1521 | }
1522 | }
1523 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/directives/NgGridItem.ts:
--------------------------------------------------------------------------------
1 | import { NgGrid } from './NgGrid';
2 | import { NgGridItemConfig, NgGridItemEvent, NgGridItemPosition, NgGridItemSize, NgGridRawPosition, NgGridItemDimensions, ResizeHandle } from '../interfaces/INgGrid';
3 | import { Directive, ElementRef, Renderer2, EventEmitter, KeyValueDiffer, KeyValueDiffers, OnInit, OnDestroy, ViewContainerRef, Output, DoCheck } from '@angular/core';
4 |
5 | @Directive({
6 | selector: '[ngGridItem]',
7 | inputs: ['config: ngGridItem']
8 | })
9 | export class NgGridItem implements OnInit, OnDestroy, DoCheck {
10 | // Event Emitters
11 | @Output() public onItemChange: EventEmitter = new EventEmitter(false);
12 | @Output() public onDragStart: EventEmitter = new EventEmitter();
13 | @Output() public onDrag: EventEmitter = new EventEmitter();
14 | @Output() public onDragStop: EventEmitter = new EventEmitter();
15 | @Output() public onDragAny: EventEmitter = new EventEmitter();
16 | @Output() public onResizeStart: EventEmitter = new EventEmitter();
17 | @Output() public onResize: EventEmitter = new EventEmitter();
18 | @Output() public onResizeStop: EventEmitter = new EventEmitter();
19 | @Output() public onResizeAny: EventEmitter = new EventEmitter();
20 | @Output() public onChangeStart: EventEmitter = new EventEmitter();
21 | @Output() public onChange: EventEmitter = new EventEmitter();
22 | @Output() public onChangeStop: EventEmitter = new EventEmitter();
23 | @Output() public onChangeAny: EventEmitter = new EventEmitter();
24 | @Output() public ngGridItemChange: EventEmitter = new EventEmitter();
25 |
26 | // Default config
27 | private static CONST_DEFAULT_CONFIG: NgGridItemConfig = {
28 | uid: null,
29 | col: 1,
30 | row: 1,
31 | sizex: 1,
32 | sizey: 1,
33 | dragHandle: null,
34 | resizeHandle: null,
35 | fixed: false,
36 | draggable: true,
37 | resizable: true,
38 | borderSize: 25,
39 | resizeDirections: null,
40 | };
41 |
42 | public isFixed: boolean = false;
43 | public isDraggable: boolean = true;
44 | public isResizable: boolean = true;
45 | public minWidth: number = 0;
46 | public minHeight: number = 0;
47 | public uid: string = null;
48 |
49 | // Private variables
50 | private _payload: any;
51 | private _currentPosition: NgGridItemPosition = { col: 1, row: 1 };
52 | private _size: NgGridItemSize = { x: 1, y: 1 };
53 | private _config = NgGridItem.CONST_DEFAULT_CONFIG;
54 | private _userConfig = null;
55 | private _dragHandle: string;
56 | private _resizeHandle: ResizeHandle;
57 | private _borderSize: number;
58 | private _elemWidth: number;
59 | private _elemHeight: number;
60 | private _elemLeft: number;
61 | private _elemTop: number;
62 | private _added: boolean = false;
63 | private _differ: KeyValueDiffer;
64 | private _cascadeMode: string;
65 | private _maxCols: number = 0;
66 | private _minCols: number = 0;
67 | private _maxRows: number = 0;
68 | private _minRows: number = 0;
69 | private _resizeDirections: string[] = [];
70 | private _zIndex: number = 0;
71 |
72 | set zIndex(zIndex: number) {
73 | this._renderer.setStyle(this._ngEl.nativeElement, 'z-index', zIndex.toString());
74 | this._zIndex = zIndex;
75 | }
76 |
77 | get zIndex(): number {
78 | return this._zIndex;
79 | }
80 |
81 | // [ng-grid-item] handler
82 | set config(v: NgGridItemConfig) {
83 | this._userConfig = v;
84 |
85 | const configObject = Object.assign({}, NgGridItem.CONST_DEFAULT_CONFIG, v);
86 | for (let x in NgGridItem.CONST_DEFAULT_CONFIG)
87 | if (configObject[x] == null)
88 | configObject[x] = NgGridItem.CONST_DEFAULT_CONFIG[x];
89 |
90 | this.setConfig(configObject);
91 |
92 | if (this._userConfig != null) {
93 | if (this._differ == null) {
94 | this._differ = this._differs.find(this._userConfig).create();
95 | }
96 |
97 | this._differ.diff(this._userConfig);
98 | }
99 |
100 | if (!this._added) {
101 | this._added = true;
102 | this._ngGrid.addItem(this);
103 | }
104 |
105 | this._recalculateDimensions();
106 | this._recalculatePosition();
107 | }
108 |
109 | get sizex(): number {
110 | return this._size.x;
111 | }
112 |
113 | get sizey(): number {
114 | return this._size.y;
115 | }
116 |
117 | get col(): number {
118 | return this._currentPosition.col;
119 | }
120 |
121 | get row(): number {
122 | return this._currentPosition.row;
123 | }
124 |
125 | get currentCol(): number {
126 | return this._currentPosition.col;
127 | }
128 |
129 | get currentRow(): number {
130 | return this._currentPosition.row;
131 | }
132 |
133 | // Constructor
134 | constructor(
135 | private _differs: KeyValueDiffers,
136 | private _ngEl: ElementRef,
137 | private _renderer: Renderer2,
138 | private _ngGrid: NgGrid,
139 | public containerRef: ViewContainerRef,
140 | ) { }
141 |
142 | public onResizeStartEvent(): void {
143 | const event: NgGridItemEvent = this.getEventOutput();
144 | this.onResizeStart.emit(event);
145 | this.onResizeAny.emit(event);
146 | this.onChangeStart.emit(event);
147 | this.onChangeAny.emit(event);
148 | }
149 | public onResizeEvent(): void {
150 | const event: NgGridItemEvent = this.getEventOutput();
151 | this.onResize.emit(event);
152 | this.onResizeAny.emit(event);
153 | this.onChange.emit(event);
154 | this.onChangeAny.emit(event);
155 | }
156 | public onResizeStopEvent(): void {
157 | const event: NgGridItemEvent = this.getEventOutput();
158 | this.onResizeStop.emit(event);
159 | this.onResizeAny.emit(event);
160 | this.onChangeStop.emit(event);
161 | this.onChangeAny.emit(event);
162 |
163 | this.onConfigChangeEvent();
164 | }
165 | public onDragStartEvent(): void {
166 | const event: NgGridItemEvent = this.getEventOutput();
167 | this.onDragStart.emit(event);
168 | this.onDragAny.emit(event);
169 | this.onChangeStart.emit(event);
170 | this.onChangeAny.emit(event);
171 | }
172 | public onDragEvent(): void {
173 | const event: NgGridItemEvent = this.getEventOutput();
174 | this.onDrag.emit(event);
175 | this.onDragAny.emit(event);
176 | this.onChange.emit(event);
177 | this.onChangeAny.emit(event);
178 | }
179 | public onDragStopEvent(): void {
180 | const event: NgGridItemEvent = this.getEventOutput();
181 | this.onDragStop.emit(event);
182 | this.onDragAny.emit(event);
183 | this.onChangeStop.emit(event);
184 | this.onChangeAny.emit(event);
185 |
186 | this.onConfigChangeEvent();
187 | }
188 | public onCascadeEvent(): void {
189 | this.onConfigChangeEvent();
190 | }
191 |
192 | public ngOnInit(): void {
193 | this._renderer.addClass(this._ngEl.nativeElement, 'grid-item');
194 | if (this._ngGrid.autoStyle) this._renderer.setStyle(this._ngEl.nativeElement, 'position', 'absolute');
195 | this._recalculateDimensions();
196 | this._recalculatePosition();
197 |
198 | // Force a config update in case there is no config assigned
199 | this.config = this._userConfig;
200 | }
201 |
202 | // Public methods
203 | public canDrag(e: any): boolean {
204 | if (!this.isDraggable) return false;
205 |
206 | if (this._dragHandle) {
207 | return this.findHandle(this._dragHandle, e.target);
208 | }
209 |
210 | return true;
211 | }
212 |
213 | public findHandle(handleSelector: string, startElement: HTMLElement): boolean {
214 | try {
215 | let targetElem: any = startElement;
216 |
217 | while (targetElem && targetElem != this._ngEl.nativeElement) {
218 | if (this.elementMatches(targetElem, handleSelector)) return true;
219 |
220 | targetElem = targetElem.parentElement;
221 | }
222 | } catch (err) {}
223 |
224 | return false;
225 | }
226 |
227 | public canResize(e: any): string {
228 | if (!this.isResizable) return null;
229 |
230 | if (this._resizeHandle) {
231 | if (typeof this._resizeHandle === 'string') {
232 | return this.findHandle(this._resizeHandle, e.target) ? 'bottomright' : null;
233 | }
234 |
235 | if (typeof this._resizeHandle !== 'object') return null;
236 |
237 | const resizeDirections = [ 'bottomright', 'bottomleft', 'topright', 'topleft', 'right', 'left', 'bottom', 'top' ];
238 | for (let direction of resizeDirections) {
239 | if (direction in this._resizeHandle) {
240 | if (this.findHandle(this._resizeHandle[direction], e.target)) {
241 | return direction;
242 | }
243 | }
244 | }
245 |
246 | return null;
247 | }
248 |
249 | if (this._borderSize <= 0) return null;
250 |
251 | const mousePos: NgGridRawPosition = this._getMousePosition(e);
252 |
253 | for (let direction of this._resizeDirections) {
254 | if (this.canResizeInDirection(direction, mousePos)) {
255 | return direction;
256 | }
257 | }
258 |
259 | return null;
260 | }
261 |
262 | public onMouseMove(e: any): void {
263 | if (this._ngGrid.autoStyle) {
264 | if (this._ngGrid.resizeEnable) {
265 | const resizeDirection = this.canResize(e);
266 |
267 | let cursor: string = 'default';
268 | switch (resizeDirection) {
269 | case 'bottomright':
270 | case 'topleft':
271 | cursor = 'nwse-resize';
272 | break;
273 | case 'topright':
274 | case 'bottomleft':
275 | cursor = 'nesw-resize';
276 | break;
277 | case 'top':
278 | case 'bottom':
279 | cursor = 'ns-resize';
280 | break;
281 | case 'left':
282 | case 'right':
283 | cursor = 'ew-resize';
284 | break;
285 | default:
286 | if (this._ngGrid.dragEnable && this.canDrag(e)) {
287 | cursor = 'move';
288 | }
289 | break;
290 | }
291 |
292 | this._renderer.setStyle(this._ngEl.nativeElement, 'cursor', cursor);
293 | } else if (this._ngGrid.dragEnable && this.canDrag(e)) {
294 | this._renderer.setStyle(this._ngEl.nativeElement, 'cursor', 'move');
295 | } else {
296 | this._renderer.setStyle(this._ngEl.nativeElement, 'cursor', 'default');
297 | }
298 | }
299 | }
300 |
301 | public ngOnDestroy(): void {
302 | if (this._added) this._ngGrid.removeItem(this);
303 | }
304 |
305 | // Getters
306 | public getElement(): ElementRef {
307 | return this._ngEl;
308 | }
309 |
310 | public getDragHandle(): string {
311 | return this._dragHandle;
312 | }
313 |
314 | public getResizeHandle(): ResizeHandle {
315 | return this._resizeHandle;
316 | }
317 |
318 | public getDimensions(): NgGridItemDimensions {
319 | return { 'width': this._elemWidth, 'height': this._elemHeight };
320 | }
321 |
322 | public getSize(): NgGridItemSize {
323 | return this._size;
324 | }
325 |
326 | public getPosition(): NgGridRawPosition {
327 | return { 'left': this._elemLeft, 'top': this._elemTop };
328 | }
329 |
330 | public getGridPosition(): NgGridItemPosition {
331 | return this._currentPosition;
332 | }
333 |
334 | // Setters
335 | public setConfig(config: NgGridItemConfig): void {
336 | this._config = config;
337 |
338 | this._payload = config.payload;
339 | this._currentPosition.col = config.col ? config.col : NgGridItem.CONST_DEFAULT_CONFIG.col;
340 | this._currentPosition.row = config.row ? config.row : NgGridItem.CONST_DEFAULT_CONFIG.row;
341 | this._size.x = config.sizex ? config.sizex : NgGridItem.CONST_DEFAULT_CONFIG.sizex;
342 | this._size.y = config.sizey ? config.sizey : NgGridItem.CONST_DEFAULT_CONFIG.sizey;
343 | this._dragHandle = config.dragHandle;
344 | this._resizeHandle = config.resizeHandle;
345 | this._borderSize = config.borderSize;
346 | this.isDraggable = config.draggable ? true : false;
347 | this.isResizable = config.resizable ? true : false;
348 | this.isFixed = config.fixed ? true : false;
349 | this._resizeDirections = config.resizeDirections || this._ngGrid.resizeDirections;
350 |
351 | this._maxCols = !isNaN(config.maxCols) && isFinite(config.maxCols) ? config.maxCols : 0;
352 | this._minCols = !isNaN(config.minCols) && isFinite(config.minCols) ? config.minCols : 0;
353 | this._maxRows = !isNaN(config.maxRows) && isFinite(config.maxRows) ? config.maxRows : 0;
354 | this._minRows = !isNaN(config.minRows) && isFinite(config.minRows) ? config.minRows : 0;
355 |
356 | this.minWidth = !isNaN(config.minWidth) && isFinite(config.minWidth) ? config.minWidth : 0;
357 | this.minHeight = !isNaN(config.minHeight) && isFinite(config.minHeight) ? config.minHeight : 0;
358 |
359 | if (this._minCols > 0 && this._maxCols > 0 && this._minCols > this._maxCols) this._minCols = 0;
360 | if (this._minRows > 0 && this._maxRows > 0 && this._minRows > this._maxRows) this._minRows = 0;
361 |
362 | if (this._added) {
363 | this._ngGrid.updateItem(this);
364 | }
365 |
366 | this._size = this.fixResize(this._size);
367 |
368 | this._recalculatePosition();
369 | this._recalculateDimensions();
370 | }
371 |
372 | public ngDoCheck(): boolean {
373 | if (this._differ != null) {
374 | const changes: any = this._differ.diff(this._userConfig);
375 |
376 | if (changes != null) {
377 | return this._applyChanges(changes);
378 | }
379 | }
380 |
381 | return false;
382 | }
383 |
384 | public setSize(newSize: NgGridItemSize, update: boolean = true): void {
385 | newSize = this.fixResize(newSize);
386 | this._size = newSize;
387 | if (update) this._recalculateDimensions();
388 |
389 | this.onItemChange.emit(this.getEventOutput());
390 | }
391 |
392 | public setGridPosition(gridPosition: NgGridItemPosition, update: boolean = true): void {
393 | this._currentPosition = gridPosition;
394 | if (update) this._recalculatePosition();
395 |
396 | this.onItemChange.emit(this.getEventOutput());
397 | }
398 |
399 | public getEventOutput(): NgGridItemEvent {
400 | return {
401 | uid: this.uid,
402 | payload: this._payload,
403 | col: this._currentPosition.col,
404 | row: this._currentPosition.row,
405 | sizex: this._size.x,
406 | sizey: this._size.y,
407 | width: this._elemWidth,
408 | height: this._elemHeight,
409 | left: this._elemLeft,
410 | top: this._elemTop
411 | };
412 | }
413 |
414 | public setPosition(x: number, y: number): void {
415 | switch (this._cascadeMode) {
416 | case 'up':
417 | case 'left':
418 | default:
419 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', x + 'px');
420 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', y + 'px');
421 | break;
422 | case 'right':
423 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', x + 'px');
424 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', y + 'px');
425 | break;
426 | case 'down':
427 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', x + 'px');
428 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', y + 'px');
429 | break;
430 | }
431 |
432 | this._elemLeft = x;
433 | this._elemTop = y;
434 | }
435 |
436 | public setCascadeMode(cascade: string): void {
437 | this._cascadeMode = cascade;
438 | switch (cascade) {
439 | case 'up':
440 | case 'left':
441 | default:
442 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', this._elemLeft + 'px');
443 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', this._elemTop + 'px');
444 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', null);
445 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', null);
446 | break;
447 | case 'right':
448 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', this._elemLeft + 'px');
449 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', this._elemTop + 'px');
450 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', null);
451 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', null);
452 | break;
453 | case 'down':
454 | this._renderer.setStyle(this._ngEl.nativeElement, 'left', this._elemLeft + 'px');
455 | this._renderer.setStyle(this._ngEl.nativeElement, 'bottom', this._elemTop + 'px');
456 | this._renderer.setStyle(this._ngEl.nativeElement, 'right', null);
457 | this._renderer.setStyle(this._ngEl.nativeElement, 'top', null);
458 | break;
459 | }
460 | }
461 |
462 | public setDimensions(w: number, h: number): void {
463 | if (w < this.minWidth) w = this.minWidth;
464 | if (h < this.minHeight) h = this.minHeight;
465 |
466 | this._renderer.setStyle(this._ngEl.nativeElement, 'width', w + 'px');
467 | this._renderer.setStyle(this._ngEl.nativeElement, 'height', h + 'px');
468 |
469 | this._elemWidth = w;
470 | this._elemHeight = h;
471 | }
472 |
473 | public startMoving(): void {
474 | this._renderer.addClass(this._ngEl.nativeElement, 'moving');
475 | const style: any = window.getComputedStyle(this._ngEl.nativeElement);
476 | if (this._ngGrid.autoStyle) this._renderer.setStyle(this._ngEl.nativeElement, 'z-index', (parseInt(style.getPropertyValue('z-index')) + 1).toString());
477 | }
478 |
479 | public stopMoving(): void {
480 | this._renderer.removeClass(this._ngEl.nativeElement, 'moving');
481 | const style: any = window.getComputedStyle(this._ngEl.nativeElement);
482 | if (this._ngGrid.autoStyle) this._renderer.setStyle(this._ngEl.nativeElement, 'z-index', (parseInt(style.getPropertyValue('z-index')) - 1).toString());
483 | }
484 |
485 | public recalculateSelf(): void {
486 | this._recalculatePosition();
487 | this._recalculateDimensions();
488 | }
489 |
490 | public fixResize(newSize: NgGridItemSize): NgGridItemSize {
491 | if (this._maxCols > 0 && newSize.x > this._maxCols) newSize.x = this._maxCols;
492 | if (this._maxRows > 0 && newSize.y > this._maxRows) newSize.y = this._maxRows;
493 |
494 | if (this._minCols > 0 && newSize.x < this._minCols) newSize.x = this._minCols;
495 | if (this._minRows > 0 && newSize.y < this._minRows) newSize.y = this._minRows;
496 |
497 | const itemWidth = (newSize.x * this._ngGrid.colWidth) + ((this._ngGrid.marginLeft + this._ngGrid.marginRight) * (newSize.x - 1));
498 | if (itemWidth < this.minWidth) newSize.x = Math.ceil((this.minWidth + this._ngGrid.marginRight + this._ngGrid.marginLeft) / (this._ngGrid.colWidth + this._ngGrid.marginRight + this._ngGrid.marginLeft));
499 |
500 | const itemHeight = (newSize.y * this._ngGrid.rowHeight) + ((this._ngGrid.marginTop + this._ngGrid.marginBottom) * (newSize.y - 1));
501 | if (itemHeight < this.minHeight) newSize.y = Math.ceil((this.minHeight + this._ngGrid.marginBottom + this._ngGrid.marginTop) / (this._ngGrid.rowHeight + this._ngGrid.marginBottom + this._ngGrid.marginTop));
502 |
503 | return newSize;
504 | }
505 |
506 | // Private methods
507 | private elementMatches(element: any, selector: string): boolean {
508 | if (!element) return false;
509 | if (element.matches) return element.matches(selector);
510 | if (element.oMatchesSelector) return element.oMatchesSelector(selector);
511 | if (element.msMatchesSelector) return element.msMatchesSelector(selector);
512 | if (element.mozMatchesSelector) return element.mozMatchesSelector(selector);
513 | if (element.webkitMatchesSelector) return element.webkitMatchesSelector(selector);
514 |
515 | if (!element.document || !element.ownerDocument) return false;
516 |
517 | const matches: any = (element.document || element.ownerDocument).querySelectorAll(selector);
518 | let i: number = matches.length;
519 | while (--i >= 0 && matches.item(i) !== element) { }
520 | return i > -1;
521 | }
522 |
523 | private _recalculatePosition(): void {
524 | const x: number = (this._ngGrid.colWidth + this._ngGrid.marginLeft + this._ngGrid.marginRight) * (this._currentPosition.col - 1) + this._ngGrid.marginLeft + this._ngGrid.screenMargin;
525 | const y: number = (this._ngGrid.rowHeight + this._ngGrid.marginTop + this._ngGrid.marginBottom) * (this._currentPosition.row - 1) + this._ngGrid.marginTop;
526 |
527 | this.setPosition(x, y);
528 | }
529 |
530 | private _recalculateDimensions(): void {
531 | if (this._size.x < this._ngGrid.minCols) this._size.x = this._ngGrid.minCols;
532 | if (this._size.y < this._ngGrid.minRows) this._size.y = this._ngGrid.minRows;
533 |
534 | const newWidth: number = (this._ngGrid.colWidth * this._size.x) + ((this._ngGrid.marginLeft + this._ngGrid.marginRight) * (this._size.x - 1));
535 | const newHeight: number = (this._ngGrid.rowHeight * this._size.y) + ((this._ngGrid.marginTop + this._ngGrid.marginBottom) * (this._size.y - 1));
536 |
537 | const w: number = Math.max(this.minWidth, this._ngGrid.minWidth, newWidth);
538 | const h: number = Math.max(this.minHeight, this._ngGrid.minHeight, newHeight);
539 |
540 | this.setDimensions(w, h);
541 | }
542 |
543 | private _getMousePosition(e: any): NgGridRawPosition {
544 | if (e.originalEvent && e.originalEvent.touches) {
545 | const oe: any = e.originalEvent;
546 | e = oe.touches.length ? oe.touches[0] : (oe.changedTouches.length ? oe.changedTouches[0] : e);
547 | } else if (e.touches) {
548 | e = e.touches.length ? e.touches[0] : (e.changedTouches.length ? e.changedTouches[0] : e);
549 | }
550 |
551 |
552 | const refPos: NgGridRawPosition = this._ngEl.nativeElement.getBoundingClientRect();
553 |
554 | return {
555 | left: e.clientX - refPos.left,
556 | top: e.clientY - refPos.top
557 | };
558 | }
559 |
560 | private _applyChanges(changes: any): boolean {
561 | let changed: boolean = false;
562 | const changeCheck = (record: any) => {
563 | if (this._config[record.key] !== record.currentValue) {
564 | this._config[record.key] = record.currentValue;
565 | changed = true;
566 | }
567 | };
568 | changes.forEachAddedItem(changeCheck);
569 | changes.forEachChangedItem(changeCheck);
570 | changes.forEachRemovedItem((record: any) => {
571 | changed = true;
572 | delete this._config[record.key];
573 | });
574 |
575 | if (changed) {
576 | this.setConfig(this._config);
577 | }
578 |
579 | return changed;
580 | }
581 |
582 | private onConfigChangeEvent() {
583 | if (this._userConfig === null) return;
584 |
585 | this._config.sizex = this._userConfig.sizex = this._size.x;
586 | this._config.sizey = this._userConfig.sizey = this._size.y;
587 | this._config.col = this._userConfig.col = this._currentPosition.col;
588 | this._config.row = this._userConfig.row = this._currentPosition.row;
589 | this.ngGridItemChange.emit(this._userConfig);
590 | }
591 |
592 | private canResizeInDirection(direction: string, mousePos: NgGridRawPosition): boolean {
593 | switch (direction) {
594 | case 'bottomright':
595 | return mousePos.left < this._elemWidth && mousePos.left > this._elemWidth - this._borderSize
596 | && mousePos.top < this._elemHeight && mousePos.top > this._elemHeight - this._borderSize; // tslint:disable-line:indent
597 | case 'bottomleft':
598 | return mousePos.left < this._borderSize && mousePos.top < this._elemHeight
599 | && mousePos.top > this._elemHeight - this._borderSize; // tslint:disable-line:indent
600 | case 'topright':
601 | return mousePos.left < this._elemWidth && mousePos.left > this._elemWidth - this._borderSize
602 | && mousePos.top < this._borderSize; // tslint:disable-line:indent
603 | case 'topleft':
604 | return mousePos.left < this._borderSize && mousePos.top < this._borderSize;
605 | case 'right':
606 | return mousePos.left < this._elemWidth && mousePos.left > this._elemWidth - this._borderSize;
607 | case 'left':
608 | return mousePos.left < this._borderSize;
609 | case 'bottom':
610 | return mousePos.top < this._elemHeight && mousePos.top > this._elemHeight - this._borderSize;
611 | case 'top':
612 | return mousePos.top < this._borderSize;
613 | default:
614 | return false;
615 | }
616 | }
617 | }
618 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/helpers/NgGridHelpers.ts:
--------------------------------------------------------------------------------
1 | import { NgGridItem } from "../directives/NgGridItem";
2 |
3 | export function generateUuid(): string {
4 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
5 | let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
6 | return v.toString(16);
7 | });
8 | }
9 |
10 | export function sortItemsByPositionHorizontal(a: NgGridItem, b: NgGridItem): number {
11 | if (a.col === b.col) { return a.row - b.row; }
12 | return a.col - b.col;
13 | }
14 |
15 | export function sortItemsByPositionVertical(a: NgGridItem, b: NgGridItem): number {
16 | if (a.row === b.row) { return a.col - b.col; }
17 | return a.row - b.row;
18 | }
19 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/interfaces/INgGrid.ts:
--------------------------------------------------------------------------------
1 | export type NgConfigFixDirection = 'vertical' | 'horizontal' | 'cascade';
2 |
3 | export interface NgGridConfig {
4 | margins?: number[];
5 | draggable?: boolean;
6 | resizable?: boolean;
7 | max_cols?: number;
8 | max_rows?: number;
9 | visible_cols?: number;
10 | visible_rows?: number;
11 | min_cols?: number;
12 | min_rows?: number;
13 | col_width?: number;
14 | row_height?: number;
15 | cascade?: string;
16 | min_width?: number;
17 | min_height?: number;
18 | fix_to_grid?: boolean;
19 | auto_style?: boolean;
20 | auto_resize?: boolean;
21 | maintain_ratio?: boolean;
22 | prefer_new?: boolean;
23 | zoom_on_drag?: boolean;
24 | limit_to_screen?: boolean;
25 | center_to_screen?: boolean;
26 | resize_directions?: string[];
27 | element_based_row_height?: boolean;
28 | fix_item_position_direction?: NgConfigFixDirection;
29 | fix_collision_position_direction?: NgConfigFixDirection;
30 | allow_overlap?: boolean;
31 | }
32 |
33 | export interface NgGridItemConfig {
34 | uid?: string;
35 | payload?: any;
36 | col?: number;
37 | row?: number;
38 | sizex?: number;
39 | sizey?: number;
40 | dragHandle?: string;
41 | resizeHandle?: ResizeHandle;
42 | fixed?: boolean;
43 | draggable?: boolean;
44 | resizable?: boolean;
45 | borderSize?: number;
46 | maxCols?: number;
47 | minCols?: number;
48 | maxRows?: number;
49 | minRows?: number;
50 | minWidth?: number;
51 | minHeight?: number;
52 | resizeDirections?: string[];
53 | }
54 |
55 | export interface NgGridItemEvent {
56 | uid: string;
57 | payload: any;
58 | col: number;
59 | row: number;
60 | sizex: number;
61 | sizey: number;
62 | width: number;
63 | height: number;
64 | left: number;
65 | top: number;
66 | }
67 |
68 | export interface NgGridItemSize {
69 | x: number;
70 | y: number;
71 | }
72 |
73 | export interface NgGridItemPosition {
74 | col: number;
75 | row: number;
76 | }
77 |
78 | export interface NgGridRawPosition {
79 | left: number;
80 | top: number;
81 | }
82 |
83 | export interface NgGridItemDimensions {
84 | width: number;
85 | height: number;
86 | }
87 |
88 | export type ResizeHandle = string | {
89 | bottomright?: string;
90 | bottomleft?: string;
91 | topright?: string;
92 | topleft?: string;
93 | right?: string;
94 | left?: string;
95 | bottom?: string;
96 | top?: string;
97 | };
98 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/main.ts:
--------------------------------------------------------------------------------
1 | export { NgGrid } from './directives/NgGrid';
2 | export { NgGridItem } from './directives/NgGridItem';
3 | export { NgGridPlaceholder } from './components/NgGridPlaceholder';
4 | export { NgGridConfig, NgGridItemConfig, NgGridItemEvent, NgGridItemSize, NgGridItemPosition, NgGridRawPosition, NgGridItemDimensions } from './interfaces/INgGrid';
5 | export { NgGridModule } from './modules/NgGrid.module';
6 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/modules/NgGrid.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { NgGrid } from '../directives/NgGrid';
3 | import { NgGridItem } from '../directives/NgGridItem';
4 | import { NgGridPlaceholder } from '../components/NgGridPlaceholder';
5 |
6 | @NgModule({
7 | declarations: [ NgGrid, NgGridItem, NgGridPlaceholder ],
8 | entryComponents: [ NgGridPlaceholder ],
9 | exports: [ NgGrid, NgGridItem ]
10 | })
11 | export class NgGridModule {}
12 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/public_api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of angular2-grid
3 | */
4 |
5 | export * from './lib/angular2-grid.service';
6 | export * from './lib/angular2-grid.component';
7 | export * from './lib/angular2-grid.module';
8 |
--------------------------------------------------------------------------------
/projects/angular2-grid/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'core-js/es7/reflect';
4 | import 'zone.js/dist/zone';
5 | import 'zone.js/dist/zone-testing';
6 | import { getTestBed } from '@angular/core/testing';
7 | import {
8 | BrowserDynamicTestingModule,
9 | platformBrowserDynamicTesting
10 | } from '@angular/platform-browser-dynamic/testing';
11 |
12 | declare const require: any;
13 |
14 | // First, initialize the Angular testing environment.
15 | getTestBed().initTestEnvironment(
16 | BrowserDynamicTestingModule,
17 | platformBrowserDynamicTesting()
18 | );
19 | // Then we find all the tests.
20 | const context = require.context('./', true, /\.spec\.ts$/);
21 | // And load the modules.
22 | context.keys().map(context);
23 |
--------------------------------------------------------------------------------
/projects/angular2-grid/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "target": "es2015",
6 | "module": "es2015",
7 | "moduleResolution": "node",
8 | "declaration": true,
9 | "sourceMap": true,
10 | "inlineSources": true,
11 | "emitDecoratorMetadata": true,
12 | "experimentalDecorators": true,
13 | "importHelpers": true,
14 | "types": [],
15 | "lib": [
16 | "dom",
17 | "es2015"
18 | ]
19 | },
20 | "angularCompilerOptions": {
21 | "annotateForClosureCompiler": true,
22 | "skipTemplateCodegen": true,
23 | "strictMetadataEmit": true,
24 | "fullTemplateTypeCheck": true,
25 | "strictInjectionParameters": true,
26 | "flatModuleId": "AUTOGENERATED",
27 | "flatModuleOutFile": "AUTOGENERATED",
28 | "enableIvy": false
29 | },
30 | "exclude": [
31 | "src/test.ts",
32 | "**/*.spec.ts"
33 | ]
34 | }
--------------------------------------------------------------------------------
/projects/angular2-grid/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/projects/angular2-grid/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "lib",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "lib",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | /*global jasmine */
5 | const { SpecReporter } = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | beforeLaunch: function() {
24 | require('ts-node').register({
25 | project: 'e2e/tsconfig.e2e.json'
26 | });
27 | },
28 | onPrepare: function() {
29 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | .grid-item {
2 | border: solid 1px;
3 | }
4 |
5 | .handle {
6 | width: 100%;
7 | height: 50px;
8 | background: blue;
9 | color: white;
10 | padding: 15px;
11 | box-sizing: border-box;
12 | }
13 |
14 | .btn {
15 | margin-left: 10px;
16 | }
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Angular 2 Grid Demo
4 |
This is a simple demo used for creation/testing of angular2-grid , a grid-based drag/drop/resize plugin directive for Angular 2 . It makes use of angular directives so that in order to include it, all you need to do is add an "[ngGrid]" attribute to your container and an "[ngGridItem]" to each of your grid items. It even works with the ngFor directive to add boxes on the fly!
5 |
6 |
193 |
194 |
Randomise Boxes
195 |
Add Box
196 |
197 |
198 |
199 |
226 |
227 |
228 | {{box.id}}
229 |
230 |
246 |
247 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild, AfterViewInit } from '@angular/core';
2 | import { NgGridConfig, NgGridItemConfig, NgGridItemEvent, NgGrid } from 'angular2-grid';
3 |
4 | interface Box {
5 | id: number;
6 | config: NgGridItemConfig;
7 | }
8 |
9 | @Component({
10 | selector: 'app-root',
11 | templateUrl: './app.component.html',
12 | styleUrls: ['./app.component.css']
13 | })
14 | export class AppComponent implements AfterViewInit {
15 | @ViewChild(NgGrid)
16 | private grid: NgGrid;
17 | public curNum = 10;
18 | public boxes: Array = [];
19 | public gridConfig: NgGridConfig = {
20 | 'margins': [5],
21 | 'draggable': true,
22 | 'resizable': true,
23 | 'max_cols': 0,
24 | 'max_rows': 0,
25 | 'visible_cols': 0,
26 | 'visible_rows': 0,
27 | 'min_cols': 1,
28 | 'min_rows': 1,
29 | 'col_width': 2,
30 | 'row_height': 2,
31 | 'cascade': 'up',
32 | 'min_width': 50,
33 | 'min_height': 50,
34 | 'fix_to_grid': false,
35 | 'auto_style': true,
36 | 'auto_resize': false,
37 | 'maintain_ratio': false,
38 | 'prefer_new': false,
39 | 'zoom_on_drag': false,
40 | 'limit_to_screen': true,
41 | 'element_based_row_height': false,
42 | 'center_to_screen': false,
43 | 'fix_item_position_direction': 'horizontal',
44 | 'fix_collision_position_direction': 'horizontal',
45 | };
46 | private rgb = '#efefef';
47 | private curItemCheck = 0;
48 | private itemPositions: Array = [];
49 |
50 | constructor() {
51 | for (let i = 1; i < this.curNum; i++) {
52 | const conf = this._generateDefaultItemConfig();
53 | conf.payload = i;
54 | this.boxes[i - 1] = { id: i, config: conf };
55 | }
56 | }
57 |
58 | get ratioDisabled(): boolean {
59 | return (this.gridConfig.max_rows > 0 && this.gridConfig.visible_cols > 0) ||
60 | (this.gridConfig.max_cols > 0 && this.gridConfig.visible_rows > 0) ||
61 | (this.gridConfig.visible_cols > 0 && this.gridConfig.visible_rows > 0);
62 | }
63 |
64 | get itemCheck(): number {
65 | return this.curItemCheck;
66 | }
67 |
68 | set itemCheck(v: number) {
69 | this.curItemCheck = v;
70 | }
71 |
72 | get curItem(): NgGridItemConfig {
73 | return this.boxes[this.curItemCheck] ? this.boxes[this.curItemCheck].config : {};
74 | }
75 |
76 | ngAfterViewInit(): void {
77 | // Do something with NgGrid instance here
78 | }
79 |
80 | setMargin(marginSize: string): void {
81 | this.gridConfig.margins = [ parseInt(marginSize, 10) ];
82 | }
83 |
84 | addBox(): void {
85 | const conf: NgGridItemConfig = this._generateDefaultItemConfig();
86 | conf.payload = this.curNum++;
87 | this.boxes.push({ id: conf.payload, config: conf });
88 | }
89 |
90 | removeBox(): void {
91 | if (this.boxes[this.curItemCheck]) {
92 | this.boxes.splice(this.curItemCheck, 1);
93 | }
94 | }
95 |
96 | updateItem(index: number, event: NgGridItemEvent): void {
97 | // Do something here
98 | }
99 |
100 | onDrag(index: number, event: NgGridItemEvent): void {
101 | // Do something here
102 | }
103 |
104 | onResize(index: number, event: NgGridItemEvent): void {
105 | // Do something here
106 | }
107 |
108 | public randomise(): void {
109 | for (const box of this.boxes) {
110 | box.config.col = Math.floor(Math.random() * 6) + 1;
111 | box.config.row = 1;
112 | }
113 | }
114 |
115 | private _generateDefaultItemConfig(): NgGridItemConfig {
116 | return { 'dragHandle': '.handle', 'col': 1, 'row': 1, 'sizex': 1, 'sizey': 1 };
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { HttpModule } from '@angular/http';
5 | import { NgGridModule } from "angular2-grid";
6 |
7 | import { AppComponent } from './app.component';
8 |
9 | @NgModule({
10 | declarations: [
11 | AppComponent
12 | ],
13 | imports: [
14 | BrowserModule,
15 | FormsModule,
16 | HttpModule,
17 | NgGridModule
18 | ],
19 | providers: [],
20 | bootstrap: [AppComponent]
21 | })
22 | export class AppModule { }
23 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BTMorton/angular2-grid/3ba01d30ba1ffdc19798af498daff59339d3a251/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular 2 Grid Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 | import { enableProdMode } from '@angular/core';
3 | import { environment } from './environments/environment';
4 | import { AppModule } from './app/app.module';
5 |
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic().bootstrapModule(AppModule);
11 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | // This file includes polyfills needed by Angular and is loaded before
2 | // the app. You can add your own extra polyfills to this file.
3 | import 'core-js/es6/symbol';
4 | import 'core-js/es6/object';
5 | import 'core-js/es6/function';
6 | import 'core-js/es6/parse-int';
7 | import 'core-js/es6/parse-float';
8 | import 'core-js/es6/number';
9 | import 'core-js/es6/math';
10 | import 'core-js/es6/string';
11 | import 'core-js/es6/date';
12 | import 'core-js/es6/array';
13 | import 'core-js/es6/regexp';
14 | import 'core-js/es6/map';
15 | import 'core-js/es6/set';
16 | import 'core-js/es6/reflect';
17 |
18 | import 'core-js/es7/reflect';
19 | import 'zone.js/dist/zone';
20 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es5",
9 | "lib": [
10 | "es2017",
11 | "dom"
12 | ],
13 | "outDir": "../out-tsc/app",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": [],
17 | "paths": {
18 | "angular2-grid": [
19 | "../dist/angular2-grid"
20 | ]
21 | }
22 | },
23 | "exclude": [
24 | "test.ts",
25 | "**/*.spec.ts"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2017",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/spec",
13 | "module": "commonjs",
14 | "target": "es5",
15 | "baseUrl": "",
16 | "types": [
17 | "jasmine",
18 | "node"
19 | ],
20 | "paths": {
21 | "angular2-grid": [
22 | "../dist/angular2-grid"
23 | ]
24 | }
25 | },
26 | "files": [
27 | "test.ts",
28 | "polyfills.ts"
29 | ],
30 | "include": [
31 | "**/*.spec.ts",
32 | "**/*.d.ts"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "baseUrl": "src",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "moduleResolution": "node",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "node_modules/@types"
14 | ],
15 | "lib": [
16 | "es2017",
17 | "dom"
18 | ],
19 | "paths": {
20 | "angular2-grid": [
21 | "dist/angular2-grid"
22 | ]
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "import-blacklist": [true],
16 | "import-spacing": true,
17 | "indent": [
18 | true,
19 | "spaces"
20 | ],
21 | "interface-over-type-literal": true,
22 | "label-position": true,
23 | "max-line-length": [
24 | true,
25 | 140
26 | ],
27 | "member-access": false,
28 | "member-ordering": [
29 | true,
30 | "static-before-instance",
31 | "variables-before-functions"
32 | ],
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-construct": true,
44 | "no-debugger": true,
45 | "no-duplicate-variable": true,
46 | "no-empty": false,
47 | "no-empty-interface": true,
48 | "no-eval": true,
49 | "no-inferrable-types": [true, "ignore-params"],
50 | "no-shadowed-variable": true,
51 | "no-string-literal": false,
52 | "no-string-throw": true,
53 | "no-switch-case-fall-through": true,
54 | "no-trailing-whitespace": true,
55 | "no-unused-expression": true,
56 | "no-use-before-declare": true,
57 | "no-var-keyword": true,
58 | "object-literal-sort-keys": false,
59 | "one-line": [
60 | true,
61 | "check-open-brace",
62 | "check-catch",
63 | "check-else",
64 | "check-whitespace"
65 | ],
66 | "prefer-const": true,
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "radix": true,
72 | "semicolon": [
73 | "always"
74 | ],
75 | "triple-equals": [
76 | true,
77 | "allow-null-check"
78 | ],
79 | "typedef-whitespace": [
80 | true,
81 | {
82 | "call-signature": "nospace",
83 | "index-signature": "nospace",
84 | "parameter": "nospace",
85 | "property-declaration": "nospace",
86 | "variable-declaration": "nospace"
87 | }
88 | ],
89 | "typeof-compare": true,
90 | "unified-signatures": true,
91 | "variable-name": false,
92 | "whitespace": [
93 | true,
94 | "check-branch",
95 | "check-decl",
96 | "check-operator",
97 | "check-separator",
98 | "check-type"
99 | ],
100 |
101 | "directive-selector": [true, "attribute", "app", "camelCase"],
102 | "component-selector": [true, "element", "app", "kebab-case"],
103 | "use-input-property-decorator": true,
104 | "use-output-property-decorator": true,
105 | "use-host-property-decorator": true,
106 | "no-input-rename": true,
107 | "no-output-rename": true,
108 | "use-life-cycle-interface": true,
109 | "use-pipe-transform-interface": true,
110 | "component-class-suffix": true,
111 | "directive-class-suffix": true,
112 | "no-access-missing-member": true,
113 | "templates-use-public": true,
114 | "invoke-injectable": true
115 | }
116 | }
117 |
--------------------------------------------------------------------------------