将 gridstack.js 包装到 Angular 2 组件中

Posted

技术标签:

【中文标题】将 gridstack.js 包装到 Angular 2 组件中【英文标题】:Wrap gridstack.js into Angular 2 component 【发布时间】:2017-02-15 12:32:00 【问题描述】:

我有一些 Angular 1 的经验,但我们需要在 Angular 2 项目中使用gridstack.js。

我们熟悉 gridstack-angular 项目,但该项目在 Angular 1 中。我认为我遇到的最大问题是 Angular 2 概念。

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

教程

好的,对于初学者来说,Angular 2 Quickstart 是最好的。

然后继续并移动到Tour of Heroes。这也是一个很棒的教程。

工具

对于教程,坦率地说,构建任何 Angular 2 应用程序,我强烈建议使用Angular-Cli。它使构建 Angular 2 应用程序变得轻而易举

看看 Angular-Cli 的 Table of Contents 看看它能做什么

示例


my-grid-stack.component.html

<div class="grid-stack">
    <div class="grid-stack-item"
        data-gs-x="0" data-gs-y="0"
        data-gs- data-gs->
            <div class="grid-stack-item-content"></div>
    </div>
    <div class="grid-stack-item"
        data-gs-x="4" data-gs-y="0"
        data-gs- data-gs->
            <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);
  


然后我会将gridstack.js 文件放在src/assets/libs/gridstack 文件夹中。

然后别忘了在你的index.html中导入

<script src="assets/libs/gridstack/gridstack.js"></script>

【讨论】:

哦!好吧,是的!我目前正在为您提供一个示例,说明如何将 gridstack 与 Angular 2 一起使用 @user3758236 查看帖子,添加“示例”部分 终于开始测试这个......我做了一些改变,但你的基本想法很棒,给了我一个起点。我最终创建了 GridStackDirective 和一个 GridStackItemDirective,我将在下面发布。 @user3758236 耶!谢谢!很高兴能帮忙!是的,我有兴趣看看你做了什么!干得好! 你有同样的工作回购吗?这会很有帮助。【参考方案2】:

我们最终创建了两个指令:GridStackDirective 和 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>

我们尝试在 gridstack.js 网页上复制演示网格。如果您要运行它并且希望它看起来像他们的,您需要从他们的站点获取一些 .css 文件、.js 文件等。

【讨论】:

谢谢老兄,非常有用!! 但仍有 1 个问题。我们如何在网格堆栈项中放置一个角度组件,例如: 。我试过了,但它没有渲染! @ArkieCoder - 你有同样的工作回购吗?这会很有帮助。【参考方案3】:

基于用户@Etchelon 和@user3758236 的回答

为了方便使用,我创建了这个 gridstack angular 4 库模块

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

它有一个简单的用法,我在那里添加了一个用于动态生成小部件的示例

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

干杯:)

【讨论】:

【参考方案4】:

根据@user3758236 的回答,我开发了几个组件,而不仅仅是指令:

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(); 
    

后一个组件使用一个服务来创建动态组件,你可以在 *** 的其他地方找到它。

用法如下:

<grid-stack  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>

【讨论】:

DynamicComponentService 呢?

以上是关于将 gridstack.js 包装到 Angular 2 组件中的主要内容,如果未能解决你的问题,请参考以下文章

gridstack.js 根据内部内容设置 grid-stack-item 高度自动

gridstack.js - 保持纵横比

如何在 Angular6 项目中使用 gridstack.js

在 gridstack.js 中,拖动功能不起作用

如何使 Gridstack.js 小部件垂直响应?

Gridstack - 添加带有内容的新小部件