├── .gitignore ├── .npmignore ├── .travis.yml ├── .vs ├── ProjectSettings.json ├── VSWorkspaceState.json ├── config │ └── applicationhost.config ├── ng4-gridstack │ └── v15 │ │ └── .suo └── slnx.sqlite ├── .yo-rc.json ├── LICENSE ├── README.md ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── grid-stack-item.component.ts ├── grid-stack-item.model.ts ├── grid-stack-options.model.ts ├── grid-stack.component.spec.ts ├── grid-stack.component.ts ├── grid-stack.directive.ts ├── grid-stack.pipe.ts ├── grid-stack.service.ts ├── index.ts ├── package.json ├── tsconfig.es5.json └── tsconfig.spec.json ├── tools └── gulp │ └── inline-resources.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/* 3 | npm-debug.log 4 | 5 | # TypeScript 6 | src/*.js 7 | src/*.map 8 | src/*.d.ts 9 | 10 | # JetBrains 11 | .idea 12 | .project 13 | .settings 14 | .idea/* 15 | *.iml 16 | 17 | # VS Code 18 | .vscode/* 19 | 20 | # Windows 21 | Thumbs.db 22 | Desktop.ini 23 | 24 | # Mac 25 | .DS_Store 26 | **/.DS_Store 27 | 28 | # Ngc generated files 29 | **/*.ngfactory.ts 30 | 31 | # Build files 32 | dist/* 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/* 3 | npm-debug.log 4 | docs/* 5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM 6 | # TypeScript 7 | # *.js 8 | # *.map 9 | # *.d.ts 10 | 11 | # JetBrains 12 | .idea 13 | .project 14 | .settings 15 | .idea/* 16 | *.iml 17 | 18 | # VS Code 19 | .vscode/* 20 | 21 | # Windows 22 | Thumbs.db 23 | Desktop.ini 24 | 25 | # Mac 26 | .DS_Store 27 | **/.DS_Store 28 | 29 | # Ngc generated files 30 | **/*.ngfactory.ts 31 | 32 | # Library files 33 | build/* 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4.2.1' 5 | -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "", 4 | "\\src" 5 | ], 6 | "SelectedNode": "\\src\\grid-stack.component.ts", 7 | "PreviewInSolutionExplorer": false 8 | } -------------------------------------------------------------------------------- /.vs/config/applicationhost.config: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 49 | 50 | 51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 60 | 61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 | 84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 |
99 |
100 | 101 |
102 |
103 | 104 |
105 |
106 | 107 |
108 |
109 |
110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 | 119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | -------------------------------------------------------------------------------- /.vs/ng4-gridstack/v15/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramyothman/ng4-gridstack/c6c83a49f8b279bab3066a570e38a8e322b5e98b/.vs/ng4-gridstack/v15/.suo -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramyothman/ng4-gridstack/c6c83a49f8b279bab3066a570e38a8e322b5e98b/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular2-library": { 3 | "promptValues": { 4 | "gitRepositoryUrl": "https://github.com/ramyothman/ng4-gridstack" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ramy Othman 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ng4-gridstack 2 | 3 | ## Installation 4 | 5 | To install this library, run: 6 | 7 | ```bash 8 | $ npm install ng4-gridstack --save 9 | ``` 10 | 11 | ## Consuming your library 12 | 13 | Once you have published your library to npm, you can import your library in any Angular application by running: 14 | 15 | ```bash 16 | $ npm install ng4-gridstack 17 | ``` 18 | 19 | Then add reference to the gridstask.js and lodash to your index.html for how to do it check their site at 20 | - https://github.com/gridstack/gridstack.js 21 | 22 | and then from your Angular `AppModule`: 23 | 24 | ```typescript 25 | import { BrowserModule } from '@angular/platform-browser'; 26 | import { NgModule } from '@angular/core'; 27 | 28 | import { AppComponent } from './app.component'; 29 | 30 | // Import your library 31 | import { GridStackModule } from 'ng4-gridstack'; 32 | 33 | @NgModule({ 34 | declarations: [ 35 | AppComponent 36 | ], 37 | imports: [ 38 | BrowserModule, 39 | 40 | // Specify your library as an import 41 | GridStackModule 42 | ], 43 | providers: [], 44 | bootstrap: [AppComponent] 45 | }) 46 | export class AppModule { } 47 | ``` 48 | 49 | Once your library is imported, you can use its components, directives and pipes in your Angular application: 50 | 51 | ```xml 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | If you want to dynamically generate widgets: 62 | 63 | ```xml 64 | 65 | 66 | 67 | 68 |
69 |
{{widget.Caption}}
70 |
71 |
72 | 73 |
74 |
75 |
76 | ``` 77 | ```typescript 78 | import { Component, OnInit, ViewChildren, QueryList, ViewChild, ChangeDetectorRef } from '@angular/core'; 79 | import { GridStackItem, GridStackOptions, GridStackItemComponent, GridStackComponent} from 'ng4-gridstack' 80 | 81 | @Component({ 82 | selector: 'app-grid-stack', 83 | templateUrl: './app-grid-stack.component.html' 84 | }) 85 | export class DashboardComponent implements OnInit { 86 | @ViewChildren(GridStackItemComponent) items: QueryList; 87 | @ViewChild('gridStackMain') gridStackMain: GridStackComponent; 88 | area: GridStackOptions = new GridStackOptions(); 89 | widgets: GridStackItem[] = []; 90 | 91 | constructor(private cd: ChangeDetectorRef) { 92 | 93 | } 94 | 95 | AddWidget(widgetType: DashboardWidgetTypeEnum) { 96 | var widget = new GridStackItem(); 97 | 98 | widget.width = 6; 99 | widget.height = 4; 100 | widget.x = 0; 101 | widget.y = 0; 102 | this.widgets.push(widget); 103 | this.cd.detectChanges(); 104 | var arr = this.items.toArray(); 105 | this.gridStackMain.AddWidget(arr[this.items.length - 1]); 106 | } 107 | } 108 | ``` 109 | 110 | ## Development 111 | 112 | To generate all `*.js`, `*.d.ts` and `*.metadata.json` files: 113 | 114 | ```bash 115 | $ npm run build 116 | ``` 117 | 118 | To lint all `*.ts` files: 119 | 120 | ```bash 121 | $ npm run lint 122 | ``` 123 | ## Credit 124 | 125 | I was looking for integrating cleanly the amazing gridstack.js (https://github.com/gridstack/gridstack.js) library with angular 4 and found this thread on stack overflow 126 | https://stackoverflow.com/questions/39901473/wrap-gridstack-js-into-angular-2-component 127 | Credit Goes to Answers from those users 128 | https://stackoverflow.com/users/3758236/user3758236 129 | https://stackoverflow.com/users/3112339/etchelon 130 | 131 | ## License 132 | 133 | MIT © [Ramy Othman](mailto:ramy.mostafa@gmail.com) 134 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var gulp = require('gulp'), 3 | path = require('path'), 4 | ngc = require('@angular/compiler-cli/src/main').main, 5 | rollup = require('gulp-rollup'), 6 | rename = require('gulp-rename'), 7 | del = require('del'), 8 | runSequence = require('run-sequence'), 9 | inlineResources = require('./tools/gulp/inline-resources'); 10 | 11 | const rootFolder = path.join(__dirname); 12 | const srcFolder = path.join(rootFolder, 'src'); 13 | const tmpFolder = path.join(rootFolder, '.tmp'); 14 | const buildFolder = path.join(rootFolder, 'build'); 15 | const distFolder = path.join(rootFolder, 'dist'); 16 | 17 | /** 18 | * 1. Delete /dist folder 19 | */ 20 | gulp.task('clean:dist', function () { 21 | 22 | // Delete contents but not dist folder to avoid broken npm links 23 | // when dist directory is removed while npm link references it. 24 | return deleteFolders([distFolder + '/**', '!' + distFolder]); 25 | }); 26 | 27 | /** 28 | * 2. Clone the /src folder into /.tmp. If an npm link inside /src has been made, 29 | * then it's likely that a node_modules folder exists. Ignore this folder 30 | * when copying to /.tmp. 31 | */ 32 | gulp.task('copy:source', function () { 33 | return gulp.src([`${srcFolder}/**/*`, `!${srcFolder}/node_modules`]) 34 | .pipe(gulp.dest(tmpFolder)); 35 | }); 36 | 37 | /** 38 | * 3. Inline template (.html) and style (.css) files into the the component .ts files. 39 | * We do this on the /.tmp folder to avoid editing the original /src files 40 | */ 41 | gulp.task('inline-resources', function () { 42 | return Promise.resolve() 43 | .then(() => inlineResources(tmpFolder)); 44 | }); 45 | 46 | 47 | /** 48 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all 49 | * compiled modules to the /build folder. 50 | */ 51 | gulp.task('ngc', function () { 52 | return ngc({ 53 | project: `${tmpFolder}/tsconfig.es5.json` 54 | }) 55 | .then((exitCode) => { 56 | if (exitCode === 1) { 57 | // This error is caught in the 'compile' task by the runSequence method callback 58 | // so that when ngc fails to compile, the whole compile process stops running 59 | throw new Error('ngc compilation failed'); 60 | } 61 | }); 62 | }); 63 | 64 | /** 65 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the 66 | * generated file into the /dist folder 67 | */ 68 | gulp.task('rollup:fesm', function () { 69 | return gulp.src(`${buildFolder}/**/*.js`) 70 | // transform the files here. 71 | .pipe(rollup({ 72 | 73 | // Bundle's entry point 74 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry 75 | entry: `${buildFolder}/index.js`, 76 | 77 | // Allow mixing of hypothetical and actual files. "Actual" files can be files 78 | // accessed by Rollup or produced by plugins further down the chain. 79 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system 80 | // when subdirectories are used in the `src` directory. 81 | allowRealFiles: true, 82 | 83 | // A list of IDs of modules that should remain external to the bundle 84 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external 85 | external: [ 86 | '@angular/core', 87 | '@angular/common' 88 | ], 89 | 90 | // Format of generated bundle 91 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format 92 | format: 'es' 93 | })) 94 | .pipe(gulp.dest(distFolder)); 95 | }); 96 | 97 | /** 98 | * 6. Run rollup inside the /build folder to generate our UMD module and place the 99 | * generated file into the /dist folder 100 | */ 101 | gulp.task('rollup:umd', function () { 102 | return gulp.src(`${buildFolder}/**/*.js`) 103 | // transform the files here. 104 | .pipe(rollup({ 105 | 106 | // Bundle's entry point 107 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry 108 | entry: `${buildFolder}/index.js`, 109 | 110 | // Allow mixing of hypothetical and actual files. "Actual" files can be files 111 | // accessed by Rollup or produced by plugins further down the chain. 112 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system 113 | // when subdirectories are used in the `src` directory. 114 | allowRealFiles: true, 115 | 116 | // A list of IDs of modules that should remain external to the bundle 117 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external 118 | external: [ 119 | '@angular/core', 120 | '@angular/common' 121 | ], 122 | 123 | // Format of generated bundle 124 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format 125 | format: 'umd', 126 | 127 | // Export mode to use 128 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#exports 129 | exports: 'named', 130 | 131 | // The name to use for the module for UMD/IIFE bundles 132 | // (required for bundles with exports) 133 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename 134 | moduleName: 'ng4-gridstackstack', 135 | 136 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals 137 | globals: { 138 | typescript: 'ts' 139 | } 140 | 141 | })) 142 | .pipe(rename('ng4-gridstackstack.umd.js')) 143 | .pipe(gulp.dest(distFolder)); 144 | }); 145 | 146 | /** 147 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build 148 | * because with don't need individual modules anymore, just the Flat ES module generated 149 | * on step 5. 150 | */ 151 | gulp.task('copy:build', function () { 152 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`]) 153 | .pipe(gulp.dest(distFolder)); 154 | }); 155 | 156 | /** 157 | * 8. Copy package.json from /src to /dist 158 | */ 159 | gulp.task('copy:manifest', function () { 160 | return gulp.src([`${srcFolder}/package.json`]) 161 | .pipe(gulp.dest(distFolder)); 162 | }); 163 | 164 | /** 165 | * 9. Copy README.md from / to /dist 166 | */ 167 | gulp.task('copy:readme', function () { 168 | return gulp.src([path.join(rootFolder, 'README.MD')]) 169 | .pipe(gulp.dest(distFolder)); 170 | }); 171 | 172 | /** 173 | * 10. Delete /.tmp folder 174 | */ 175 | gulp.task('clean:tmp', function () { 176 | return deleteFolders([tmpFolder]); 177 | }); 178 | 179 | /** 180 | * 11. Delete /build folder 181 | */ 182 | gulp.task('clean:build', function () { 183 | return deleteFolders([buildFolder]); 184 | }); 185 | 186 | gulp.task('compile', function () { 187 | runSequence( 188 | 'clean:dist', 189 | 'copy:source', 190 | 'inline-resources', 191 | 'ngc', 192 | 'rollup:fesm', 193 | 'rollup:umd', 194 | 'copy:build', 195 | 'copy:manifest', 196 | 'copy:readme', 197 | 'clean:build', 198 | 'clean:tmp', 199 | function (err) { 200 | if (err) { 201 | console.log('ERROR:', err.message); 202 | deleteFolders([distFolder, tmpFolder, buildFolder]); 203 | } else { 204 | console.log('Compilation finished succesfully'); 205 | } 206 | }); 207 | }); 208 | 209 | /** 210 | * Watch for any change in the /src folder and compile files 211 | */ 212 | gulp.task('watch', function () { 213 | gulp.watch(`${srcFolder}/**/*`, ['compile']); 214 | }); 215 | 216 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']); 217 | 218 | gulp.task('build', ['clean', 'compile']); 219 | gulp.task('build:watch', ['build', 'watch']); 220 | gulp.task('default', ['build:watch']); 221 | 222 | /** 223 | * Deletes the specified folder 224 | */ 225 | function deleteFolders(folders) { 226 | return del(folders); 227 | } 228 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng4-gridstack", 3 | "version": "0.3.40", 4 | "scripts": { 5 | "build": "gulp build", 6 | "build:watch": "gulp", 7 | "docs": "npm run docs:build", 8 | "docs:build": "compodoc -p tsconfig.json -n ng4-gridstack -d docs --hideGenerator", 9 | "docs:serve": "npm run docs:build -- -s", 10 | "docs:watch": "npm run docs:build -- -s -w", 11 | "lint": "tslint --type-check --project tsconfig.json src/**/*.ts", 12 | "test": "tsc && karma start" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ramyothman/ng4-gridstack" 17 | }, 18 | "author": { 19 | "name": "Ramy Othman", 20 | "email": "ramy.mostafa@gmail.com" 21 | }, 22 | "keywords": [ 23 | "angular" 24 | ], 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/ramyothman/ng4-gridstack/issues" 28 | }, 29 | "devDependencies": { 30 | "@angular/common": "^4.0.0", 31 | "@angular/compiler": "^4.0.0", 32 | "@angular/compiler-cli": "^4.0.0", 33 | "@angular/core": "^4.0.0", 34 | "@angular/platform-browser": "^4.0.0", 35 | "@angular/platform-browser-dynamic": "^4.0.0", 36 | "@compodoc/compodoc": "^1.0.0-beta.10", 37 | "@types/jasmine": "2.5.38", 38 | "@types/node": "~6.0.60", 39 | "codelyzer": "~2.0.0", 40 | "core-js": "^2.4.1", 41 | "del": "^2.2.2", 42 | "gulp": "^3.9.1", 43 | "gulp-rename": "^1.2.2", 44 | "gulp-rollup": "^2.11.0", 45 | "jasmine-core": "~2.5.2", 46 | "jasmine-spec-reporter": "~3.2.0", 47 | "karma": "~1.4.1", 48 | "karma-chrome-launcher": "~2.0.0", 49 | "karma-cli": "~1.0.1", 50 | "karma-coverage-istanbul-reporter": "^0.2.0", 51 | "karma-jasmine": "~1.1.0", 52 | "karma-jasmine-html-reporter": "^0.2.2", 53 | "node-sass": "^4.5.2", 54 | "node-sass-tilde-importer": "^1.0.0", 55 | "node-watch": "^0.5.2", 56 | "protractor": "~5.1.0", 57 | "rollup": "^0.41.6", 58 | "run-sequence": "^1.2.2", 59 | "rxjs": "^5.1.0", 60 | "ts-node": "~2.0.0", 61 | "tslint": "~4.5.0", 62 | "typescript": "~2.2.0", 63 | "zone.js": "^0.8.4" 64 | }, 65 | "engines": { 66 | "node": ">=6.0.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/grid-stack-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, HostBinding, Input, Output, OnInit, ComponentRef, ElementRef, ViewChild, Renderer, EventEmitter, OnDestroy, AfterViewInit, ViewContainerRef } from '@angular/core'; 2 | import { GridStackItem } from './grid-stack-item.model' 3 | declare var jQuery: any; 4 | declare var _: any; 5 | 6 | @Component({ 7 | selector: 'grid-stack-item', 8 | template: `
9 |
10 | 11 |
` 12 | }) 13 | export class GridStackItemComponent implements OnInit, OnDestroy, AfterViewInit { 14 | @ViewChild("contentPlaceholder", { read: ViewContainerRef }) contentPlaceholder: ViewContainerRef; 15 | @Input() contentTemplate: string; 16 | @Input() option: GridStackItem; 17 | @Output() onGridConfigurationChanged = new EventEmitter(); 18 | 19 | contentComponentRef: ComponentRef = null; 20 | jGridRef: any = null; 21 | public jWidgetRef: any = null; 22 | 23 | constructor(private el: ElementRef, private renderer: Renderer) { 24 | this.jWidgetRef = el.nativeElement; 25 | } 26 | get nativeElement(): HTMLElement { 27 | return this.el.nativeElement; 28 | } 29 | ngOnInit() { 30 | this.RenderWidget(null); 31 | } 32 | 33 | UpdateWidget(item: GridStackItem) { 34 | 35 | } 36 | RenderWidget(item: GridStackItem) { 37 | let renderer = this.renderer; 38 | if (item != null) 39 | this.option = item; 40 | 41 | this.renderer.setElementAttribute(this.nativeElement, "style", "margin-left:" + this.option.marginWidth + ";"); 42 | this.renderer.setElementAttribute(this.nativeElement, "data-gs-x", String(this.option.x)); 43 | this.renderer.setElementAttribute(this.nativeElement, "data-gs-y", String(this.option.y)); 44 | this.renderer.setElementAttribute(this.nativeElement, "data-gs-width", String(this.option.width)); 45 | this.renderer.setElementAttribute(this.nativeElement, "data-gs-height", String(this.option.height)); 46 | if (this.option.minWidth) { 47 | renderer.setElementAttribute(this.nativeElement, "data-gs-min-width", String(this.option.minWidth)); 48 | } 49 | if (this.option.noResize != null && this.option.noResize == true) { 50 | renderer.setElementAttribute(this.nativeElement, "data-gs-no-resize", "yes"); 51 | } 52 | 53 | } 54 | 55 | update(x: number, y: number, width: number, height: number): void { 56 | // console.log("here"); 57 | if (x === this.option.x && y === this.option.y && width === this.option.width && height === this.option.height) 58 | return; 59 | if (this.option != null) { 60 | this.option.x = x; 61 | this.option.y = y; 62 | this.option.width = width; 63 | this.option.height = height; 64 | 65 | var optionNew = GridStackItem.Clone(this.option); 66 | this.onGridConfigurationChanged.emit(optionNew); 67 | } 68 | } 69 | 70 | ngAfterViewInit(): void { 71 | //if (!!this.contentTemplate) { 72 | // this.componentService.getDynamicComponentFactory({ 73 | // selector: `grid-stack-item-${Date.now()}`, 74 | // template: this.contentTemplate 75 | // }) 76 | // .then(factory => { 77 | // this.contentComponentRef = this.contentPlaceholder.createComponent(factory); 78 | // }) 79 | //} 80 | } 81 | 82 | ngOnDestroy(): void { 83 | if (this.contentComponentRef !== null) 84 | this.contentComponentRef.destroy(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/grid-stack-item.model.ts: -------------------------------------------------------------------------------- 1 | 2 | export class GridStackItem { 3 | x: number = 0; 4 | y: number = 0; 5 | height: number = 1; 6 | width: number = 1; 7 | maxHeight: number; 8 | minHeight: number; 9 | maxWidth: number; 10 | minWidth: number; 11 | noResize: boolean = false; 12 | noMove: boolean; 13 | autoPosition: boolean = false; 14 | marginWidth: string = "10px"; 15 | locked: boolean; 16 | el: any; 17 | customId: string; 18 | static Clone(widget: GridStackItem) { 19 | var result = new GridStackItem(); 20 | 21 | result.autoPosition = widget.autoPosition; 22 | result.customId = widget.customId; 23 | result.el = widget.el; 24 | result.height = widget.height; 25 | result.locked = widget.locked; 26 | result.maxHeight = widget.maxHeight; 27 | result.maxWidth = widget.maxWidth; 28 | result.minHeight = widget.minHeight; 29 | result.minWidth = widget.minWidth; 30 | result.noMove = widget.noMove; 31 | result.noResize = widget.noResize; 32 | result.width = widget.width; 33 | result.x = widget.x; 34 | result.y = widget.y; 35 | 36 | return result; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/grid-stack-options.model.ts: -------------------------------------------------------------------------------- 1 | export class GridStackOptions { 2 | acceptWidgets: boolean; 3 | alwaysShowResizeHandle: boolean; 4 | animate: boolean; 5 | auto: boolean; 6 | cellHeight: string; //default is 60 could be an integer (px) a string (ex: '10em', '100px', '10rem') 0 or null 'auto' - height will be calculated from cell width. 7 | ddPlugin: boolean; 8 | disableDrag: boolean; 9 | disableResize: boolean; 10 | draggable: boolean; 11 | handle: string; //default: '.grid-stack-item-content' 12 | handleClass: string; //draggable handle class (e.g. 'grid-stack-item-content'). If set handle is ignored (default: null) 13 | height: number; //maximum rows amount. Default is 0 which means no maximum rows 14 | float: boolean; // enable floating widgets (default: false) 15 | itemClass: string; //widget class (default: 'grid-stack-item') 16 | minWidth: string; //minimal width.If window width is less, grid will be shown in one - column mode (default: 768) 17 | disableOneColumnMode: boolean; //disables the onColumnMode when the window width is less than minWidth (default: 'false') 18 | oneColumnModeClass: string; //class set on grid when in one column mode (default: 'grid-stack-one-column-mode') 19 | placeholderClass: string; //class for placeholder (default: 'grid-stack-placeholder') 20 | placeholderText: string; //placeholder default content (default: '') 21 | resizable: boolean; //allows to override jQuery UI resizable options. (default: {autoHide: true, handles: 'se'}) 22 | removable: boolean; //if true widgets could be removed by dragging outside of the grid. It could also be a jQuery selector string, in this case widgets will be removed by dropping them there (default: false) 23 | removeTimeout: number; // time in milliseconds before widget is being removed while dragging outside of the grid. (default: 2000) 24 | rtl: string; //if true turns grid to RTL. Possible values are true, false, 'auto' (default: 'auto') See example 25 | staticGrid: boolean; //makes grid static (default false). If true widgets are not movable/resizable. You don't even need jQueryUI draggable/resizable. A CSS class grid-stack-static is also added to the container. 26 | verticalMargin: number; //vertical gap size (default: 20). Can be: an integer (px) a string (ex: '2em', '20px', '2rem') 27 | width: number; //amount of columns (default: 12). Setting non-default value must be supported by equivalent change in CSS 28 | } 29 | -------------------------------------------------------------------------------- /src/grid-stack.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | import { DebugElement } from '@angular/core'; 4 | 5 | import { SampleComponent } from './sample.component'; 6 | 7 | describe('SampleComponent', () => { 8 | 9 | let comp: SampleComponent; 10 | let fixture: ComponentFixture; 11 | let de: DebugElement; 12 | let el: HTMLElement; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ SampleComponent ], // declare the test component 17 | }); 18 | 19 | fixture = TestBed.createComponent(SampleComponent); 20 | 21 | comp = fixture.componentInstance; // BannerComponent test instance 22 | 23 | // query for the title

by CSS element selector 24 | de = fixture.debugElement.query(By.css('h1')); 25 | el = de.nativeElement; 26 | }); 27 | 28 | it('Should be false', () => { 29 | expect(false).toBe(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/grid-stack.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, QueryList, Input, ContentChildren, ElementRef, Renderer, AfterContentInit } from '@angular/core'; 2 | import { GridStackOptions } from './grid-stack-options.model'; 3 | import { GridStackItem } from './grid-stack-item.model'; 4 | import { GridStackItemComponent } from './grid-stack-item.component'; 5 | declare var jQuery: any; 6 | declare var _: any; 7 | 8 | @Component({ 9 | selector: 'grid-stack', 10 | template: ``, 11 | styles: [":host { display: block; }"] 12 | }) 13 | export class GridStackComponent implements AfterContentInit { 14 | 15 | @Input() options: GridStackOptions = new GridStackOptions(); 16 | @ContentChildren(GridStackItemComponent) items: QueryList; 17 | private gridStack: any = null; 18 | private grid: any = null; 19 | private defaultOptions = { 20 | cellHeight: '60px', 21 | width: 12, 22 | height: 0, 23 | animate: true, 24 | float: false, 25 | resizable: true 26 | }; 27 | 28 | constructor(private el: ElementRef, private renderer: Renderer) { 29 | } 30 | 31 | public makeWidget(item: GridStackItemComponent) { 32 | item.jGridRef = this.grid; 33 | if (item.option != null && item.option.noResize != null && item.option.noResize) { 34 | return; 35 | } 36 | 37 | this.grid.resizable(item.nativeElement, true); 38 | this.grid.move(item.nativeElement, item.option.x, item.option.y); 39 | this.grid.resize(item.nativeElement, item.option.width, item.option.height); 40 | }; 41 | 42 | public updateWidget(item: GridStackItemComponent) { 43 | this.grid.resizable(item.nativeElement, true); 44 | this.grid.move(item.nativeElement, item.option.x, item.option.y); 45 | this.grid.resize(item.nativeElement, item.option.width, item.option.height); 46 | } 47 | 48 | public AddWidget(item: GridStackItemComponent) { 49 | item.jGridRef = this.grid; 50 | if (item.option != null && item.option.noResize != null && item.option.noResize) { 51 | return; 52 | } 53 | 54 | this.grid.resizable(item.nativeElement, true); 55 | this.grid.move(item.nativeElement, item.option.x, item.option.y); 56 | this.grid.resize(item.nativeElement, item.option.width, item.option.height); 57 | } 58 | 59 | public RemoveWidget(item: GridStackItemComponent) { 60 | this.grid.removeWidget(item.nativeElement, false); 61 | } 62 | 63 | ngAfterContentInit(): void { 64 | const that = this; 65 | let nativeElement = this.el.nativeElement; 66 | if (this.options == null) { 67 | this.options = new GridStackOptions(); 68 | } 69 | 70 | for (const key of Object.keys(this.defaultOptions)) { 71 | if (!this.options.hasOwnProperty(key)) { 72 | this.options = this.defaultOptions[key]; 73 | } 74 | } 75 | 76 | this.renderer.setElementAttribute(nativeElement, 'data-gs-width', String(this.options.width)); 77 | this.renderer.setElementAttribute(nativeElement, 'data-gs-height', String(this.options.height)); 78 | 79 | this.gridStack = jQuery(nativeElement).gridstack(this.options); 80 | this.grid = this.gridStack.data('gridstack'); 81 | 82 | this.gridStack.on('change', (e: any, items: any) => { 83 | _.each(items, (item: any) => this.widgetChanged(item)); 84 | }); 85 | 86 | // Initialize widgets 87 | this.items.forEach(item => that.makeWidget(item)); 88 | } 89 | 90 | private widgetChanged(change: GridStackItem): void { 91 | const jWidget = change.el; 92 | const gridStackItem = this.items.find(item => item.jWidgetRef !== null ? item.jWidgetRef === jWidget[0] : false); 93 | if (!gridStackItem) { 94 | return; 95 | } 96 | 97 | gridStackItem.update(change.x, change.y, change.width, change.height); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/grid-stack.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[gridStackDirective]' 5 | }) 6 | export class GridStackDirective { 7 | 8 | constructor(private el: ElementRef) { 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/grid-stack.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, PipeTransform, Pipe } from '@angular/core'; 2 | 3 | /** 4 | * Transforms any input value 5 | */ 6 | @Pipe({ 7 | name: 'gridStackPipe' 8 | }) 9 | @Injectable() 10 | export class GridStackPipe implements PipeTransform { 11 | transform(value: any, args: any[] = null): string { 12 | return value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/grid-stack.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class GridStackService { 5 | 6 | constructor() { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { GridStackComponent } from './grid-stack.component'; 4 | import { GridStackItemComponent } from './grid-stack-item.component'; 5 | import { GridStackDirective } from './grid-stack.directive'; 6 | import { GridStackPipe } from './grid-stack.pipe'; 7 | import { GridStackService } from './grid-stack.service'; 8 | 9 | import { GridStackOptions } from './grid-stack-options.model'; 10 | import { GridStackItem } from './grid-stack-item.model'; 11 | export * from './grid-stack.component'; 12 | export * from './grid-stack-item.component'; 13 | export * from './grid-stack.directive'; 14 | export * from './grid-stack.pipe'; 15 | export * from './grid-stack.service'; 16 | export * from './grid-stack-options.model'; 17 | export * from './grid-stack-item.model'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | CommonModule 22 | ], 23 | declarations: [ 24 | GridStackComponent, 25 | GridStackItemComponent, 26 | GridStackDirective, 27 | GridStackPipe 28 | ], 29 | exports: [ 30 | GridStackComponent, 31 | GridStackItemComponent, 32 | GridStackDirective, 33 | GridStackPipe 34 | ] 35 | }) 36 | export class GridStackModule { 37 | static forRoot(): ModuleWithProviders { 38 | return { 39 | ngModule: GridStackModule, 40 | providers: [GridStackService] 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng4-gridstack", 3 | "version": "0.3.40", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/ramyothman/ng4-gridstack" 7 | }, 8 | "author": { 9 | "name": "Ramy Othman", 10 | "email": "ramy.mostafa@gmail.com" 11 | }, 12 | "keywords": [ 13 | "angular" 14 | ], 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/ramyothman/ng4-gridstack/issues" 18 | }, 19 | "main": "ng4-gridstack.umd.js", 20 | "module": "ng4-gridstack.js", 21 | "jsnext:main": "ng4-gridstack.js", 22 | "typings": "ng4-gridstack.d.ts", 23 | "peerDependencies": { 24 | "@angular/core": "^4.0.0", 25 | "rxjs": "^5.1.0", 26 | "zone.js": "^0.8.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tsconfig.es5.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "es2015", 5 | "target": "es5", 6 | "baseUrl": ".", 7 | "stripInternal": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "moduleResolution": "node", 11 | "outDir": "../build", 12 | "rootDir": ".", 13 | "lib": [ 14 | "es2016", 15 | "dom" 16 | ], 17 | "skipLibCheck": true, 18 | "types": [] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "strictMetadataEmit": true, 23 | "skipTemplateCodegen": true, 24 | "flatModuleOutFile": "ng4-gridstack.js", 25 | "flatModuleId": "ng4-gridstack" 26 | }, 27 | "files": [ 28 | "./index.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.es5.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "outDir": "../out-tsc/spec", 7 | "module": "commonjs", 8 | "target": "es6", 9 | "baseUrl": "", 10 | "types": [ 11 | "jest", 12 | "node" 13 | ] 14 | }, 15 | "files": [ 16 | "**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tools/gulp/inline-resources.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const glob = require('glob'); 8 | const sass = require('node-sass'); 9 | const tildeImporter = require('node-sass-tilde-importer'); 10 | 11 | /** 12 | * Simple Promiseify function that takes a Node API and return a version that supports promises. 13 | * We use promises instead of synchronized functions to make the process less I/O bound and 14 | * faster. It also simplifies the code. 15 | */ 16 | function promiseify(fn) { 17 | return function () { 18 | const args = [].slice.call(arguments, 0); 19 | return new Promise((resolve, reject) => { 20 | fn.apply(this, args.concat([function (err, value) { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | resolve(value); 25 | } 26 | }])); 27 | }); 28 | }; 29 | } 30 | 31 | const readFile = promiseify(fs.readFile); 32 | const writeFile = promiseify(fs.writeFile); 33 | 34 | /** 35 | * Inline resources in a tsc/ngc compilation. 36 | * @param projectPath {string} Path to the project. 37 | */ 38 | function inlineResources(projectPath) { 39 | 40 | // Match only TypeScript files in projectPath. 41 | const files = glob.sync('**/*.ts', {cwd: projectPath}); 42 | 43 | // For each file, inline the templates and styles under it and write the new file. 44 | return Promise.all(files.map(filePath => { 45 | const fullFilePath = path.join(projectPath, filePath); 46 | return readFile(fullFilePath, 'utf-8') 47 | .then(content => inlineResourcesFromString(content, url => { 48 | // Resolve the template url. 49 | return path.join(path.dirname(fullFilePath), url); 50 | })) 51 | .then(content => writeFile(fullFilePath, content)) 52 | .catch(err => { 53 | console.error('An error occured: ', err); 54 | }); 55 | })); 56 | } 57 | 58 | /** 59 | * Inline resources from a string content. 60 | * @param content {string} The source file's content. 61 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 62 | * @returns {string} The content with resources inlined. 63 | */ 64 | function inlineResourcesFromString(content, urlResolver) { 65 | // Curry through the inlining functions. 66 | return [ 67 | inlineTemplate, 68 | inlineStyle, 69 | removeModuleId 70 | ].reduce((content, fn) => fn(content, urlResolver), content); 71 | } 72 | 73 | /** 74 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and 75 | * replace with `template: ...` (with the content of the file included). 76 | * @param content {string} The source file's content. 77 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 78 | * @return {string} The content with all templates inlined. 79 | */ 80 | function inlineTemplate(content, urlResolver) { 81 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) { 82 | const templateFile = urlResolver(templateUrl); 83 | const templateContent = fs.readFileSync(templateFile, 'utf-8'); 84 | const shortenedTemplate = templateContent 85 | .replace(/([\n\r]\s*)+/gm, ' ') 86 | .replace(/"/g, '\\"'); 87 | return `template: "${shortenedTemplate}"`; 88 | }); 89 | } 90 | 91 | 92 | /** 93 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and 94 | * replace with `styles: [...]` (with the content of the file included). 95 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 96 | * @param content {string} The source file's content. 97 | * @return {string} The content with all styles inlined. 98 | */ 99 | function inlineStyle(content, urlResolver) { 100 | return content.replace(/styleUrls\s*:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) { 101 | const urls = eval(styleUrls); 102 | return 'styles: [' 103 | + urls.map(styleUrl => { 104 | const styleFile = urlResolver(styleUrl); 105 | const originContent = fs.readFileSync(styleFile, 'utf-8'); 106 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent; 107 | const shortenedStyle = styleContent 108 | .replace(/([\n\r]\s*)+/gm, ' ') 109 | .replace(/"/g, '\\"'); 110 | return `"${shortenedStyle}"`; 111 | }) 112 | .join(',\n') 113 | + ']'; 114 | }); 115 | } 116 | 117 | /** 118 | * build sass content to css 119 | * @param content {string} the css content 120 | * @param sourceFile {string} the scss file sourceFile 121 | * @return {string} the generated css, empty string if error occured 122 | */ 123 | function buildSass(content, sourceFile) { 124 | try { 125 | const result = sass.renderSync({ 126 | data: content, 127 | file: sourceFile, 128 | importer: tildeImporter 129 | }); 130 | return result.css.toString() 131 | } catch (e) { 132 | console.error('\x1b[41m'); 133 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column); 134 | console.error(e.formatted); 135 | console.error('\x1b[0m'); 136 | return ""; 137 | } 138 | } 139 | 140 | /** 141 | * Remove every mention of `moduleId: module.id`. 142 | * @param content {string} The source file's content. 143 | * @returns {string} The content with all moduleId: mentions removed. 144 | */ 145 | function removeModuleId(content) { 146 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, ''); 147 | } 148 | 149 | module.exports = inlineResources; 150 | module.exports.inlineResourcesFromString = inlineResourcesFromString; 151 | 152 | // Run inlineResources if module is being called directly from the CLI with arguments. 153 | if (require.main === module && process.argv.length > 2) { 154 | console.log('Inlining resources from project:', process.argv[2]); 155 | return inlineResources(process.argv[2]); 156 | } 157 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "experimentalDecorators": true, 5 | "moduleResolution": "node", 6 | "rootDir": "./src", 7 | "target": "es5", 8 | "lib": [ 9 | "es2016", 10 | "dom" 11 | ], 12 | "skipLibCheck": true, 13 | "types": [] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "max-line-length": [ 20 | true, 21 | 140 22 | ], 23 | "member-access": false, 24 | "member-ordering": [ 25 | true, 26 | "static-before-instance", 27 | "variables-before-functions" 28 | ], 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-console": [ 32 | true, 33 | "debug", 34 | "info", 35 | "time", 36 | "timeEnd", 37 | "trace" 38 | ], 39 | "no-construct": true, 40 | "no-debugger": true, 41 | "no-duplicate-variable": true, 42 | "no-empty": false, 43 | "no-eval": true, 44 | "no-inferrable-types": true, 45 | "no-shadowed-variable": true, 46 | "no-string-literal": false, 47 | "no-switch-case-fall-through": true, 48 | "no-trailing-whitespace": true, 49 | "no-unused-expression": true, 50 | "no-unused-variable": true, 51 | "no-use-before-declare": true, 52 | "no-var-keyword": true, 53 | "object-literal-sort-keys": false, 54 | "one-line": [ 55 | true, 56 | "check-open-brace", 57 | "check-catch", 58 | "check-else", 59 | "check-whitespace" 60 | ], 61 | "quotemark": [ 62 | true, 63 | "single" 64 | ], 65 | "radix": true, 66 | "semicolon": [ 67 | "always" 68 | ], 69 | "triple-equals": [ 70 | true, 71 | "allow-null-check" 72 | ], 73 | "typedef-whitespace": [ 74 | true, 75 | { 76 | "call-signature": "nospace", 77 | "index-signature": "nospace", 78 | "parameter": "nospace", 79 | "property-declaration": "nospace", 80 | "variable-declaration": "nospace" 81 | } 82 | ], 83 | "variable-name": false, 84 | "whitespace": [ 85 | true, 86 | "check-branch", 87 | "check-decl", 88 | "check-operator", 89 | "check-separator", 90 | "check-type" 91 | ], 92 | "directive-selector": [true, "attribute", "", "camelCase"], 93 | "component-selector": [true, "element", "", "kebab-case"], 94 | "use-input-property-decorator": true, 95 | "use-output-property-decorator": true, 96 | "use-host-property-decorator": true, 97 | "no-input-rename": true, 98 | "no-output-rename": true, 99 | "use-life-cycle-interface": true, 100 | "use-pipe-transform-interface": true, 101 | "component-class-suffix": true, 102 | "directive-class-suffix": true 103 | } 104 | } 105 | --------------------------------------------------------------------------------