Wrap gridstack.js into Angular 2 component

后端 未结 4 1453
暗喜
暗喜 2020-12-15 09:20

I have some Angular 1 experience, but we have a need to use gridstack.js in an Angular 2 project.

We are familiar with the gridstack-angular project, but that proj

相关标签:
4条回答
  • 2020-12-15 09:27

    Based on Users @Etchelon and @user3758236 answers

    I have created this gridstack angular 4 library module for easy usage

    • https://github.com/ramyothman/ng4-gridstack
    • https://www.npmjs.com/package/ng4-gridstack

    It has a simple usage and I added an example there for dynamically generating widgets

    <grid-stack class="grid-stack" [options]="options">
      <grid-stack-item [option]="widget1" class="grid-stack-item"  >
      </grid-stack-item>
      <grid-stack-item [option]="widget2" class="grid-stack-item" >
      </grid-stack-item>
    </grid-stack>
    

    Cheers :)

    0 讨论(0)
  • 2020-12-15 09:29

    Tutorials

    Okay for begginers the Angular 2 Quickstart is the best.

    Then that continues and moves into the Tour of Heroes. Which is also a fantastic tutorial.

    Tool

    For the tutorials, and quite frankly building ANY Angular 2 app, I would highly recommend using Angular-Cli. It makes building Angular 2 apps a breeze

    Just take a look at the Angular-Cli's Table of Contents to see what it can do


    Example


    my-grid-stack.component.html

    <div class="grid-stack">
        <div class="grid-stack-item"
            data-gs-x="0" data-gs-y="0"
            data-gs-width="4" data-gs-height="2">
                <div class="grid-stack-item-content"></div>
        </div>
        <div class="grid-stack-item"
            data-gs-x="4" data-gs-y="0"
            data-gs-width="4" data-gs-height="4">
                <div class="grid-stack-item-content"></div>
        </div>
    </div>
    

    my-grid-stack.component.ts (How to get JQuery in Angular 2)

    import { Component, OnInit } from '@angular/core';
    declare var $: any; // JQuery
    
    @Component({
      selector: 'app-my-gridstack',
      templateUrl: './app/my-grid-stack/my-grid-stack.component.html',
      styleUrls: ['./app/my-grid-stack/my-grid-stack.component.css']
    })
    export class MyGridStackComponent implements OnInit {
    
      constructor() { }
    
      ngOnInit() {
          var options = {
              cell_height: 80,
              vertical_margin: 10
          };
          $('.grid-stack').gridstack(options);
      }
    
    }
    

    Then I would put the gridstack.js file in the src/assets/libs/gridstack folder.

    Then don't forget to import in your index.html

    <script src="assets/libs/gridstack/gridstack.js"></script>
    
    0 讨论(0)
  • 2020-12-15 09:36

    Based on @user3758236's answer I developed a couple of components, instead of having just directives:

    interfaces.ts:

    export interface IGridConfiguration {
        width: number;
        height: number;
        x: number;
        y: number;
    }
    

    grid-stack.component.ts:

    import { Component, HostBinding, OnInit, Input, OnChanges, AfterViewInit, AfterContentInit, ElementRef, Renderer, QueryList, ContentChildren } from '@angular/core';
    
    import { GridStackItemComponent } from "./grid-stack-item.component";
    import { IGridConfiguration } from "./interfaces";
    
    declare var jQuery: any; // JQuery
    declare var _: any;
    
    @Component({
        moduleId: module.id,
        selector: 'grid-stack',
        template: `<ng-content></ng-content>`,
        styles: [":host { display: block; }"]
    })
    export class GridStackComponent implements OnInit, OnChanges, AfterContentInit {
        @HostBinding("class") cssClass = "grid-stack";
        @Input() width: number;
        @Input() animate: boolean;
        @Input() float: boolean;
    
        @ContentChildren(GridStackItemComponent) items: QueryList<GridStackItemComponent>;
    
        constructor(
            private _el: ElementRef,
            private _renderer: Renderer
        ) { }
    
        private _jGrid: any = null;
        private _grid: any = null;
    
        ngOnInit() {
            let nativeElement = this._el.nativeElement;
    
            this._renderer.setElementAttribute(nativeElement, "data-gs-width", String(this.width));
            let options = {
                cellHeight: 100,
                verticalMargin: 10,
                animate: this.animate,
                auto: false,
                float: this.float
            };
    
            _.delay(() => {
                const jGrid = jQuery(nativeElement).gridstack(options);
                jGrid.on("change", (e: any, items: any) => {
                    console.log("GridStack change event! event: ", e, "items: ", items);
                    _.each(items, (item: any) => this.widgetChanged(item));
                });
                this._jGrid = jGrid;
                this._grid = this._jGrid.data("gridstack");
            }, 50);
        }
    
        ngOnChanges(): void { }
    
        ngAfterContentInit(): void {
            const makeWidget = (item: GridStackItemComponent) => {
                const widget = this._grid.makeWidget(item.nativeElement);
                item.jGridRef = this._grid;
                item.jWidgetRef = widget;
            };
    
            // Initialize widgets
            this.items.forEach(item => makeWidget(item));
    
            // Also when they are rebound
            this.items
                .changes
                .subscribe((items: QueryList<GridStackItemComponent>) => {
                    if (!this._grid) {
                        _.delay(() => this.items.notifyOnChanges(), 50);
                        return;
                    }
                    items.forEach(item => makeWidget(item));
                });
        }
    
        private widgetChanged(change: IWidgetDragStoppedEvent): void {
            var jWidget = change.el;
            var gridStackItem = this.items.find(item => item.jWidgetRef !== null ? item.jWidgetRef[0] === jWidget[0] : false);
            if (!gridStackItem)
                return; 
            gridStackItem.update(change.x, change.y, change.width, change.height);
        }
    }
    
    interface IWidgetDragStoppedEvent extends IGridConfiguration {
        el: any[];
    }
    

    grid-stack-item.component.ts

    import { Component, ComponentRef, ElementRef, Input, Output, HostBinding, Renderer } from "@angular/core";
    import { EventEmitter, OnInit, OnChanges, OnDestroy, AfterViewInit, ViewChild, ViewContainerRef } from "@angular/core";
    
    import { IGridConfiguration } from "./interfaces";
    import { DynamicComponentService } from "./dynamic-component.service";
    
    @Component({
        moduleId: module.id,
        selector: "grid-stack-item",
        template: `
    <div class="grid-stack-item-content">
        <div #contentPlaceholder></div>
        <ng-content *ngIf="!contentTemplate"></ng-content>
    </div>`,
        styleUrls: ["./grid-stack-item.component.css"]
    })
    export class GridStackItemComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
        @HostBinding("class") cssClass = "grid-stack-item";
        @ViewChild("contentPlaceholder", { read: ViewContainerRef }) contentPlaceholder: ViewContainerRef;
        @Input() initialX: number;
        @Input() initialY: number;
        @Input() initialWidth: number;
        @Input() initialHeight: number;
        @Input() minWidth: number;
        @Input() canResize: boolean;
        @Input() contentTemplate: string;
        contentComponentRef: ComponentRef<any> = null;
    
        @Output() onGridConfigurationChanged = new EventEmitter<IGridConfiguration>();
    
        private _currentX: number;
        private _currentY: number;
        private _currentWidth: number;
        private _currentHeight: number;
    
        jGridRef: any = null;
        private _jWidgetRef: any = null;
        get jWidgetRef(): any { return this._jWidgetRef; }
        set jWidgetRef(val: any) {
            if (!!this._jWidgetRef)
                this._jWidgetRef.off("change");
            this._jWidgetRef = val;
            this._jWidgetRef.on("change", function () {
                console.log("Change!!", arguments);
            });
        }
    
        update(x: number, y: number, width: number, height: number): void {
            if (x === this._currentX && y === this._currentY && width === this._currentWidth && height === this._currentHeight)
                return;
    
            this._currentX = x;
            this._currentY = y;
            this._currentWidth = width;
            this._currentHeight = height;
            this.onGridConfigurationChanged.emit({
                x: x,
                y: y,
                width: width,
                height: height
            });
        }
    
        get nativeElement(): HTMLElement {
            return this.el.nativeElement;
        }
    
        constructor(
            private el: ElementRef,
            private renderer: Renderer,
            private componentService: DynamicComponentService
        ) { }
    
        ngOnInit(): void {
            let renderer = this.renderer;
            let nativeElement = this.nativeElement;
            let cannotResize: string = this.canResize ? "yes" : "no";
    
            renderer.setElementAttribute(nativeElement, "data-gs-x", String(this.initialX));
            renderer.setElementAttribute(nativeElement, "data-gs-y", String(this.initialY));
            renderer.setElementAttribute(nativeElement, "data-gs-width", String(this.initialWidth));
            renderer.setElementAttribute(nativeElement, "data-gs-height", String(this.initialHeight));
            if (this.minWidth) {
                renderer.setElementAttribute(nativeElement, "data-gs-min-width", String(this.minWidth));
            }
            if (cannotResize == "yes") {
                renderer.setElementAttribute(nativeElement, "data-gs-no-resize", cannotResize);
            }
        }
    
        ngOnChanges(): void {
            // TODO: check that these properties are in the SimpleChanges
            this._currentX = this.initialX;
            this._currentY = this.initialY;
            this._currentWidth = this.initialWidth;
            this._currentHeight = this.initialHeight;
        }
    
        ngAfterViewInit(): void {
            if (!!this.contentTemplate) {
                this.componentService.getDynamicComponentFactory({
                    selector: `grid-stack-item-${Date.now()}`,
                    template: this.contentTemplate
                })
                .then(factory => {
                    this.contentComponentRef = this.contentPlaceholder.createComponent(factory);
                })
            }
        }
    
        ngOnDestroy(): void {
            if (this.contentComponentRef !== null)
                this.contentComponentRef.destroy(); 
        }
    }
    

    The latter component uses a service for dynamic component creation, which u can find elsewhere on stackoverflow.

    The usage is as follows:

    <grid-stack width="12" animate="true" float="true">
        <grid-stack-item *ngFor="let field of fields; let i = index;"
            [class.selected]="field.id === selectedFieldId" (click)="fieldClicked(field.id)"
            [initialX]="field.gridConfiguration.x" [initialY]="field.gridConfiguration.y"
            [initialWidth]="field.gridConfiguration.width" [initialHeight]="field.gridConfiguration.height"
            [contentTemplate]="getFieldTemplate(field)" (onGridConfigurationChanged)="fieldConfigurationChanged($event, field.id)">
        </grid-stack-item>
    </grid-stack>
    
    0 讨论(0)
  • 2020-12-15 09:46

    We ended up creating two directives: GridStackDirective and GridStackItemDirective -

    grid-stack-directive.ts:

    import { Directive, OnInit, Input, ElementRef, Renderer } from '@angular/core';
    declare var $: any; // JQuery
    
    @Directive({
        selector: '[gridStack]'
    })
    export class GridStackDirective implements OnInit {
        @Input() w: number;
        @Input() animate: boolean;
    
        constructor(
            private el: ElementRef,
            private renderer: Renderer
        ) {
            renderer.setElementAttribute(el.nativeElement, "class", "grid-stack");
        }
    
        ngOnInit() {
            let renderer = this.renderer;
            let nativeElement = this.el.nativeElement;
            let animate: string = this.animate ? "yes" : "no";
    
            renderer.setElementAttribute(nativeElement, "data-gs-width", String(this.w));
            if(animate == "yes") {
                renderer.setElementAttribute(nativeElement, "data-gs-animate", animate);
            }
    
            let options = {
                cellHeight: 80,
                verticalMargin: 10
            };
    
            // TODO: listen to an event here instead of just waiting for the time to expire
            setTimeout(function () {
                $('.grid-stack').gridstack(options);
            }, 300);
        }
    
    }
    

    grid-stack-item-directive.ts:

    import { Directive, ElementRef, Input, Renderer, OnInit } from '@angular/core';
    
    @Directive({
        selector: '[gridStackItem]'
    })
    
    export class GridStackItemDirective {
      @Input() x: number;
      @Input() y: number;
      @Input() w: number;
      @Input() h: number;
      @Input() minWidth: number;
      @Input() canResize: boolean;
    
      constructor(
        private el: ElementRef,
        private renderer: Renderer
      ) { 
        renderer.setElementAttribute(el.nativeElement, "class", "grid-stack-item");
      }
    
      ngOnInit(): void {
        let renderer = this.renderer;
        let nativeElement = this.el.nativeElement;
        let cannotResize: string = this.canResize ? "yes" : "no";
        let elementText: string = '<div class="grid-stack-item-content">' + nativeElement.innerHTML + '</div>';
        // TODO: Find the Angular(tm) way to do this ...
        nativeElement.innerHTML = elementText;
    
        renderer.setElementAttribute(nativeElement, "data-gs-x", String(this.x));
        renderer.setElementAttribute(nativeElement, "data-gs-y", String(this.y));
        renderer.setElementAttribute(nativeElement, "data-gs-width", String(this.w));
        renderer.setElementAttribute(nativeElement, "data-gs-height", String(this.h));
        if(this.minWidth) {
          renderer.setElementAttribute(nativeElement, "data-gs-min-width", String(this.minWidth));
        }
        if(cannotResize == "yes") {
          renderer.setElementAttribute(nativeElement, "data-gs-no-resize", cannotResize);
        }
      }
    }
    

    app.component.html:

    <h1>My First Grid Stack Angular 2 App</h1>
    <section id="demo" class="darklue">
        <div class="container">
            <div class="row">
                <div class="col-lg-12 text-center">
                    <h2>Demo</h2>
                    <hr class="star-light">
                </div>
            </div>
            <div gridStack w="12" animate="true">
                <div gridStackItem x="0" y="0" w="4" h="2">1</div>
                <div gridStackItem x="4" y="0" w="4" h="4">2</div>
                <div gridStackItem x="8" y="0" w="2" h="2" canResize="false" minWidth="2">
                    <span class="fa fa-hand-o-up"></span> Drag me!
                </div>
                <div gridStackItem x="10" y="0" w="2" h="2">4</div>
                <div gridStackItem x="0" y="2" w="2" h="2">5</div>
                <div gridStackItem x="2" y="2" w="2" h="4">6</div>
                <div gridStackItem x="8" y="2" w="4" h="2">7</div>
                <div gridStackItem x="0" y="4" w="2" h="2">8</div>
                <div gridStackItem x="4" y="4" w="4" h="2">9</div>
                <div gridStackItem x="8" y="4" w="2" h="2">10</div>
                <div gridStackItem x="10" y="4" w="2" h="2">11</div>
            </div>
        </div>
    </section>
    

    index.html:

    <html>
      <head>
        <title>Angular QuickStart</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="styles.css">
        <link rel="stylesheet" href="node_modules/font-awesome/css/font-awesome.min.css">
    
        <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
        <link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
        <link href="https://fonts.googleapis.com/css?family=Indie+Flower" rel='stylesheet' type='text/css'>
    
        <!-- 1. Load libraries -->
         <!-- Polyfill(s) for older browsers -->
        <script src="node_modules/core-js/client/shim.min.js"></script>
        <script src="node_modules/zone.js/dist/zone.js"></script>
        <script src="node_modules/reflect-metadata/Reflect.js"></script>
        <script src="node_modules/systemjs/dist/system.src.js"></script>
    
        <!-- 2. Configure SystemJS -->
        <script src="systemjs.config.js"></script>
        <script>
          System.import('app').catch(function(err){ console.error(err); });
        </script>
    
        <!-- jquery -->
        <script src="node_modules/jquery/dist/jquery.js"></script>
        <script src="node_modules/jquery-ui-dist/jquery-ui.min.js"></script>
        <script src="node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"></script>
        <script src="node_modules/jquery-easing/dist/jquery.easing.1.3.umd.min.js"></script>
    
        <!-- underscore and gridstack --> 
        <script src="node_modules/underscore/underscore-min.js"></script>
        <script src="node_modules/gridstack/dist/gridstack.js"></script>
        <link rel="stylesheet" href="node_modules/gridstack/dist/gridstack.min.css">
        <link rel="stylesheet" href="node_modules/gridstack/dist/gridstack-extra.min.css">
        <link rel="stylesheet" href="app/css/gridstack-demo.css">
    
        <!-- bootstrap -->
        <script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
        <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css">
    
        <!-- freelancer stuff -->
        <script src="app/js/freelancer.js"></script>
        <link rel="stylesheet" href="app/css/freelancer.css">
    
      </head>
      <!-- 3. Display the application -->
      <body>
        <my-app>Loading...</my-app>
      </body>
    </html>
    

    We tried to copy the demo grid on the gridstack.js web page. If you're going to run this and you want it to look like theirs, you'll need to grab some .css files, .js files, etc. from their site.

    0 讨论(0)
提交回复
热议问题