Skip to content

Commit 72ccd8b

Browse files
devversionvivian-hu-zz
authored andcommitted
feat(schematics): create drag-drop schematic (#13368)
* Creates a CDK schematic that can be used to generate a component that uses the CDK Drag & Drop module. The code is based on the `sorting` example of the drag-drop (just a bit trimmed to keep the code simple)
1 parent 3254992 commit 72ccd8b

File tree

12 files changed

+396
-3
lines changed

12 files changed

+396
-3
lines changed

src/cdk/schematics/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ exports_files(["#bazel_workaround.txt"])
99

1010
filegroup(
1111
name = "schematics_assets",
12-
srcs = glob(["**/*.json"]) + ["README.md"],
12+
srcs = glob(["**/files/**/*", "**/*.json"]) + ["README.md"],
1313
)
1414

1515
ts_library(
1616
name = "schematics",
1717
module_name = "@angular/cdk/schematics",
18-
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
18+
srcs = glob(["**/*.ts"], exclude=["**/files/**/*.ts", "**/*.spec.ts"]),
1919
tsconfig = ":tsconfig.json",
2020
deps = [
2121
"@npm//:@schematics/angular",
@@ -51,7 +51,7 @@ jasmine_node_test(
5151

5252
ts_library(
5353
name = "schematics_test_sources",
54-
srcs = glob(["**/*.spec.ts"]),
54+
srcs = glob(["**/*.spec.ts"], exclude = ["**/files/**/*.spec.ts"]),
5555
deps = [
5656
":schematics",
5757
"@npm//:@schematics/angular",

src/cdk/schematics/collection.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"factory": "./ng-add/index",
77
"schema": "./ng-add/schema.json",
88
"aliases": ["install"]
9+
},
10+
"drag-drop": {
11+
"description": "Generates a component using the Drag and Drop module",
12+
"factory": "./ng-generate/drag-drop/index",
13+
"schema": "./ng-generate/drag-drop/schema.json",
14+
"aliases": ["dragdrop", "drag-and-drop"]
915
}
1016
}
1117
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
.container {
2+
width: 400px;
3+
max-width: 100%;
4+
margin: 0 25px 25px 0;
5+
display: inline-block;
6+
vertical-align: top;
7+
}
8+
9+
.list {
10+
border: solid 1px #ccc;
11+
min-height: 60px;
12+
background: white;
13+
border-radius: 4px;
14+
display: block;
15+
overflow: hidden;
16+
}
17+
18+
.list-item {
19+
padding: 20px 10px;
20+
border-bottom: solid 1px #ccc;
21+
box-sizing: border-box;
22+
cursor: move;
23+
background: white;
24+
color: black;
25+
font-size: 14px;
26+
}
27+
28+
.list-item:last-child {
29+
border: none;
30+
}
31+
32+
/* Highlight the list item that is being dragged. */
33+
.cdk-drag-preview {
34+
border-radius: 4px;
35+
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
36+
0 8px 10px 1px rgba(0, 0, 0, 0.14),
37+
0 3px 14px 2px rgba(0, 0, 0, 0.12);
38+
}
39+
40+
/* Animate items as they're being sorted. */
41+
.cdk-drop-dragging .cdk-drag {
42+
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
43+
}
44+
45+
/* Animate an item that has been dropped. */
46+
.cdk-drag-animating {
47+
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);
48+
}
49+
50+
.cdk-drag-placeholder {
51+
opacity: 0;
52+
}
53+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class="container">
2+
<h2>To do</h2>
3+
4+
<cdk-drop #todoList [data]="todo" [connectedTo]="doneList" class="list" (dropped)="drop($event)">
5+
<div class="list-item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
6+
</cdk-drop>
7+
</div>
8+
9+
<div class="container">
10+
<h2>Done</h2>
11+
12+
<cdk-drop #doneList [data]="done" [connectedTo]="todoList" class="list" (dropped)="drop($event)">
13+
<div class="list-item" *ngFor="let item of done" cdkDrag>{{item}}</div>
14+
</cdk-drop>
15+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { DragDropModule } from '@angular/cdk/drag-drop';
2+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
4+
import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
5+
6+
describe('<%= classify(name) %>Component', () => {
7+
let component: <%= classify(name) %>Component;
8+
let fixture: ComponentFixture<<%= classify(name) %>Component>;
9+
10+
beforeEach(async(() => {
11+
TestBed.configureTestingModule({
12+
declarations: [ <%= classify(name) %>Component ],
13+
imports: [
14+
NoopAnimationsModule,
15+
DragDropModule,
16+
]
17+
}).compileComponents();
18+
}));
19+
20+
beforeEach(() => {
21+
fixture = TestBed.createComponent(<%= classify(name) %>Component);
22+
component = fixture.componentInstance;
23+
fixture.detectChanges();
24+
});
25+
26+
it('should compile', () => {
27+
expect(component).toBeTruthy();
28+
});
29+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Component<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
2+
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
3+
4+
@Component({
5+
selector: '<%= selector %>',<% if(inlineTemplate) { %>
6+
template: `
7+
<%= indentTextContent(resolvedFiles.template, 4) %>
8+
`,<% } else { %>
9+
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
10+
styles: [`
11+
<%= indentTextContent(resolvedFiles.stylesheet, 4) %>
12+
`],<% } else { %>
13+
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>'],<% } %><% if(!!viewEncapsulation) { %>
14+
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
15+
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
16+
})
17+
export class <%= classify(name) %>Component {
18+
todo = [
19+
'Get to work',
20+
'Pick up groceries',
21+
'Go home',
22+
'Fall asleep'
23+
];
24+
25+
done = [
26+
'Get up',
27+
'Brush teeth',
28+
'Take a shower',
29+
'Check e-mail',
30+
'Walk dog'
31+
];
32+
33+
drop(event: CdkDragDrop<string[]>) {
34+
if (event.previousContainer === event.container) {
35+
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
36+
} else {
37+
transferArrayItem(event.previousContainer.data,
38+
event.container.data,
39+
event.previousIndex,
40+
event.currentIndex);
41+
}
42+
}
43+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
2+
import {createTestApp} from '../../testing';
3+
import {getFileContent} from '@schematics/angular/utility/test';
4+
import {Schema} from './schema';
5+
6+
describe('CDK drag-drop schematic', () => {
7+
let runner: SchematicTestRunner;
8+
9+
const baseOptions: Schema = {
10+
name: 'foo',
11+
// TODO(devversion): rename project to something that is not tied to Material. This involves
12+
// updating the other tests as well because `createTestApp` is responsible for creating
13+
// the project.
14+
project: 'material',
15+
};
16+
17+
beforeEach(() => {
18+
runner = new SchematicTestRunner('schematics', require.resolve('../../collection.json'));
19+
});
20+
21+
it('should create drag-drop files and add them to module', () => {
22+
const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner));
23+
const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts');
24+
const files = tree.files;
25+
26+
expect(files).toContain('/projects/material/src/app/foo/foo.component.css');
27+
expect(files).toContain('/projects/material/src/app/foo/foo.component.html');
28+
expect(files).toContain('/projects/material/src/app/foo/foo.component.spec.ts');
29+
expect(files).toContain('/projects/material/src/app/foo/foo.component.ts');
30+
31+
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/);
32+
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m);
33+
});
34+
35+
it('should add drag-drop module', () => {
36+
const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner));
37+
const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts');
38+
39+
expect(moduleContent).toContain('DragDropModule');
40+
});
41+
42+
describe('styleext option', () => {
43+
it('should respect the option value', () => {
44+
const tree = runner.runSchematic(
45+
'drag-drop', {styleext: 'scss', ...baseOptions}, createTestApp(runner));
46+
47+
expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.scss');
48+
});
49+
50+
it('should fall back to the @schematics/angular:component option value', () => {
51+
const tree = runner.runSchematic(
52+
'drag-drop', baseOptions, createTestApp(runner, {style: 'less'}));
53+
54+
expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.less');
55+
});
56+
});
57+
58+
describe('inlineStyle option', () => {
59+
it('should respect the option value', () => {
60+
const tree = runner.runSchematic(
61+
'drag-drop', {inlineStyle: true, ...baseOptions}, createTestApp(runner));
62+
63+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css');
64+
});
65+
66+
it('should fall back to the @schematics/angular:component option value', () => {
67+
const tree = runner.runSchematic(
68+
'drag-drop', baseOptions, createTestApp(runner, {inlineStyle: true}));
69+
70+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css');
71+
});
72+
});
73+
74+
describe('inlineTemplate option', () => {
75+
it('should respect the option value', () => {
76+
const tree = runner.runSchematic(
77+
'drag-drop', {inlineTemplate: true, ...baseOptions}, createTestApp(runner));
78+
79+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html');
80+
});
81+
82+
it('should fall back to the @schematics/angular:component option value', () => {
83+
const tree = runner.runSchematic(
84+
'drag-drop', baseOptions, createTestApp(runner, {inlineTemplate: true}));
85+
86+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html');
87+
});
88+
});
89+
90+
describe('spec option', () => {
91+
it('should respect the option value', () => {
92+
const tree = runner.runSchematic(
93+
'drag-drop', {spec: false, ...baseOptions}, createTestApp(runner));
94+
95+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts');
96+
});
97+
98+
it('should fall back to the @schematics/angular:component option value', () => {
99+
const tree = runner.runSchematic(
100+
'drag-drop', baseOptions, createTestApp(runner, {skipTests: true}));
101+
102+
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts');
103+
});
104+
});
105+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {chain, noop, Rule, Tree} from '@angular-devkit/schematics';
10+
import {addModuleImportToModule, buildComponent, findModuleFromOptions} from '../../utils';
11+
import {Schema} from './schema';
12+
13+
/** Scaffolds a new Angular component that uses the Drag and Drop module. */
14+
export default function(options: Schema): Rule {
15+
return chain([
16+
buildComponent({...options}, {
17+
template: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html',
18+
stylesheet: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__',
19+
}),
20+
options.skipImport ? noop() : addDragDropModulesToModule(options)
21+
]);
22+
}
23+
24+
/** Adds the required modules to the main module of the CLI project. */
25+
function addDragDropModulesToModule(options: Schema) {
26+
return (host: Tree) => {
27+
const modulePath = findModuleFromOptions(host, options)!;
28+
29+
addModuleImportToModule(host, modulePath, 'DragDropModule', '@angular/cdk/drag-drop');
30+
return host;
31+
};
32+
}

0 commit comments

Comments
 (0)