Skip to content

Commit 0e1818f

Browse files
andrewseguinjelbourn
authored andcommittedJan 17, 2017
fix(plunker): make calls to plunker synchronous to avoid popup block (#83)
* fix(plunker): make calls to plunker synchronous to avoid popup block * make linter happy * add comment on disabled
1 parent db78ef8 commit 0e1818f

File tree

4 files changed

+98
-71
lines changed

4 files changed

+98
-71
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<!-- TODO: change the template to be plunker icon -->
2-
<button md-icon-button type="button" (click)="openPlunker()"
3-
[mdTooltip]="'Edit in Plunker'">
4-
<md-icon>
5-
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
6-
<path d="M0 0h24v24H0z" fill="none"/>
7-
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
8-
</svg>
9-
</md-icon>
10-
</button>
2+
<div [mdTooltip]="isDisabled ? 'Building Plunker example...' : 'Edit in Plunker'">
3+
<button md-icon-button type="button"
4+
(click)="openPlunker()"
5+
[disabled]="isDisabled">
6+
<md-icon>
7+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
8+
<path d="M0 0h24v24H0z" fill="none"/>
9+
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
10+
</svg>
11+
</md-icon>
12+
</button>
13+
</div>

‎material.angular.io/src/app/shared/plunker/plunker-button.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,32 @@ import { ExampleData } from '../../examples/example-data';
66
selector: 'plunker-button',
77
templateUrl: './plunker-button.html',
88
providers: [PlunkerWriter],
9+
host: {
10+
'(mouseover)': 'isDisabled = !plunkerForm'
11+
}
912
})
1013
export class PlunkerButton {
11-
exampleData: ExampleData;
14+
/**
15+
* The button becomes disabled if the user hovers over the button before the plunker form
16+
* is created. After the form is created, the button becomes enabled again.
17+
* The form creation usually happens extremely quickly, but we handle the case of the
18+
* plunker not yet being ready for people will poor network connections or slow devices.
19+
*/
20+
isDisabled: boolean = false;
21+
plunkerForm: HTMLFormElement;
1222

1323
@Input()
1424
set example(example: string) {
15-
this.exampleData = new ExampleData(example);
25+
const exampleData = new ExampleData(example);
26+
this.plunkerWriter.constructPlunkerForm(exampleData).then(plunkerForm => {
27+
this.plunkerForm = plunkerForm;
28+
this.isDisabled = false;
29+
});
1630
}
1731

1832
constructor(private plunkerWriter: PlunkerWriter) {}
1933

2034
openPlunker(): void {
21-
this.plunkerWriter.openPlunker(this.exampleData);
35+
this.plunkerForm.submit();
2236
}
2337
}

‎material.angular.io/src/app/shared/plunker/plunker-writer.spec.ts

+26-27
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {ExampleData} from '../../examples/example-data';
66

77

88
describe('PlunkerWriter', () => {
9-
var plunkerWriter: PlunkerWriter;
10-
var data: ExampleData;
9+
let plunkerWriter: PlunkerWriter;
10+
let data: ExampleData;
1111
beforeEach(async(() => {
1212
TestBed.configureTestingModule({
1313
imports: [],
@@ -37,7 +37,7 @@ describe('PlunkerWriter', () => {
3737
});
3838

3939
plunkerWriter = TestBed.get(PlunkerWriter);
40-
data = new ExampleData();
40+
data = new ExampleData('');
4141
data.examplePath = 'http://material.angular.io/';
4242
data.exampleFiles = ['test.ts', 'test.html', 'src/detail.ts'];
4343
}));
@@ -63,45 +63,44 @@ describe('PlunkerWriter', () => {
6363
});
6464

6565
it('should add files to form input', () => {
66-
plunkerWriter.form = plunkerWriter._createFormElement();
66+
let form = plunkerWriter._createFormElement();
6767

68-
plunkerWriter._addFileToForm('NoContent', 'test.ts', 'path/to/file');
69-
plunkerWriter._addFileToForm('Test', 'test.html', 'path/to/file');
70-
plunkerWriter._addFileToForm('Detail', 'src/detail.ts', 'path/to/file');
68+
plunkerWriter._addFileToForm(form, data, 'NoContent', 'test.ts', 'path/to/file');
69+
plunkerWriter._addFileToForm(form, data, 'Test', 'test.html', 'path/to/file');
70+
plunkerWriter._addFileToForm(form, data, 'Detail', 'src/detail.ts', 'path/to/file');
7171

72-
let elements = plunkerWriter.form.elements;
73-
expect(elements.length).toBe(3);
74-
expect(elements[0].getAttribute('name')).toBe('files[test.ts]');
75-
expect(elements[1].getAttribute('name')).toBe('files[test.html]');
76-
expect(elements[2].getAttribute('name')).toBe('files[src/detail.ts]');
72+
expect(form.elements.length).toBe(3);
73+
expect(form.elements[0].getAttribute('name')).toBe('files[test.ts]');
74+
expect(form.elements[1].getAttribute('name')).toBe('files[test.html]');
75+
expect(form.elements[2].getAttribute('name')).toBe('files[src/detail.ts]');
7776
});
7877

7978
it('should open a new window with plunker url', fakeAsync(() => {
80-
plunkerWriter.openPlunker(data);
79+
let form;
80+
plunkerWriter.constructPlunkerForm(data).then(result => form = result);
8181
flushMicrotasks();
8282

83-
let elements = plunkerWriter.form.elements;
84-
expect(elements.length).toBe(11);
83+
expect(form.elements.length).toBe(11);
8584

8685
// Should have correct tags
87-
expect(elements[0].getAttribute('name')).toBe('tags[0]');
88-
expect(elements[0].getAttribute('value')).toBe('angular');
89-
expect(elements[1].getAttribute('value')).toBe('material');
90-
expect(elements[2].getAttribute('value')).toBe('example');
86+
expect(form.elements[0].getAttribute('name')).toBe('tags[0]');
87+
expect(form.elements[0].getAttribute('value')).toBe('angular');
88+
expect(form.elements[1].getAttribute('value')).toBe('material');
89+
expect(form.elements[2].getAttribute('value')).toBe('example');
9190

9291
// Should have private and description
93-
expect(elements[3].getAttribute('name')).toBe('private');
94-
expect(elements[4].getAttribute('name')).toBe('description');
92+
expect(form.elements[3].getAttribute('name')).toBe('private');
93+
expect(form.elements[4].getAttribute('name')).toBe('description');
9594

9695
// Should have example files
97-
expect(elements[5].getAttribute('name')).toBe('files[index.html]');
98-
expect(elements[6].getAttribute('name')).toBe('files[systemjs.config.js]');
99-
expect(elements[7].getAttribute('name')).toBe('files[main.ts]');
96+
expect(form.elements[5].getAttribute('name')).toBe('files[index.html]');
97+
expect(form.elements[6].getAttribute('name')).toBe('files[systemjs.config.js]');
98+
expect(form.elements[7].getAttribute('name')).toBe('files[main.ts]');
10099

101100
// Should have template files
102-
expect(elements[8].getAttribute('name')).toBe('files[test.ts]');
103-
expect(elements[9].getAttribute('name')).toBe('files[test.html]');
104-
expect(elements[10].getAttribute('name')).toBe('files[src/detail.ts]');
101+
expect(form.elements[8].getAttribute('name')).toBe('files[test.ts]');
102+
expect(form.elements[9].getAttribute('name')).toBe('files[test.html]');
103+
expect(form.elements[10].getAttribute('name')).toBe('files[src/detail.ts]');
105104

106105
// TODO(tinagao): Add more test
107106
}));

‎material.angular.io/src/app/shared/plunker/plunker-writer.ts

+43-32
Original file line numberDiff line numberDiff line change
@@ -34,58 +34,67 @@ const TAGS: string[] = ['angular', 'material', 'example'];
3434
*/
3535
@Injectable()
3636
export class PlunkerWriter {
37-
form: HTMLFormElement;
38-
exampleData: ExampleData;
39-
4037
constructor(private _http: Http) {}
4138

42-
/** Construct the plunker content */
43-
openPlunker(data: ExampleData) {
44-
this.exampleData = data;
45-
46-
this.form = this._createFormElement();
39+
/**
40+
* Returns an HTMLFormElement that will open a new plunker template with the example data when
41+
* called with submit().
42+
*/
43+
constructPlunkerForm(data: ExampleData): Promise<HTMLFormElement> {
44+
let form = this._createFormElement();
4745

48-
for (let i = 0; i < TAGS.length; i++) {
49-
this._createFormInput(`tags[${i}]`, TAGS[i]);
50-
}
46+
TAGS.forEach((tag, i) => this._appendFormInput(form, `tags[${i}]`, tag));
47+
this._appendFormInput(form, 'private', 'true');
48+
this._appendFormInput(form, 'description', data.description);
5149

52-
this._createFormInput('private', 'true');
53-
this._createFormInput('description', this.exampleData.description);
50+
return new Promise(resolve => {
51+
let templateContents = TEMPLATE_FILES
52+
.map(file => this._readFile(form, data, file, TEMPLATE_PATH));
5453

55-
var templateContents = TEMPLATE_FILES.map((file) => this._readFile(file, TEMPLATE_PATH));
56-
var exampleContents = this.exampleData.exampleFiles.map(
57-
(file) => this._readFile(file, this.exampleData.examplePath));
54+
let exampleContents = data.exampleFiles
55+
.map(file => this._readFile(form, data, file, data.examplePath));
5856

59-
Promise.all(templateContents.concat(exampleContents)).then((_) => this.form.submit());
57+
Promise.all(templateContents.concat(exampleContents)).then(() => {
58+
resolve(form);
59+
});
60+
});
6061
}
6162

63+
/** Constructs a new form element that will navigate to the plunker url. */
6264
_createFormElement(): HTMLFormElement {
63-
var form = document.createElement('form');
65+
const form = document.createElement('form');
6466
form.action = PLUNKER_URL;
6567
form.method = 'post';
6668
form.target = '_blank';
6769
return form;
6870
}
6971

70-
_createFormInput(name: string, value: string) {
71-
var input = document.createElement('input');
72+
/** Appends the name and value as an input to the form. */
73+
_appendFormInput(form: HTMLFormElement, name: string, value: string): void {
74+
const input = document.createElement('input');
7275
input.type = 'hidden';
7376
input.name = name;
7477
input.value = value;
75-
this.form.appendChild(input);
78+
form.appendChild(input);
7679
}
7780

78-
_readFile(filename: string, path: string) {
79-
return this._http.get(path + filename).toPromise().then(
80-
response => this._addFileToForm(response.text(), filename, path),
81+
/** Reads the file and adds its text to the form */
82+
_readFile(form: HTMLFormElement, data: ExampleData, filename: string, path: string): void {
83+
this._http.get(path + filename).toPromise().then(
84+
response => this._addFileToForm(form, data, response.text(), filename, path),
8185
error => console.log(error));
8286
}
8387

84-
_addFileToForm(content: string, filename: string, path: string) {
88+
/** Adds the file text to the form. */
89+
_addFileToForm(form: HTMLFormElement,
90+
data: ExampleData,
91+
content: string,
92+
filename: string,
93+
path: string) {
8594
if (path == TEMPLATE_PATH) {
86-
content = this._replaceExamplePlaceholderNames(filename, content);
95+
content = this._replaceExamplePlaceholderNames(data, filename, content);
8796
}
88-
this._createFormInput(`files[${filename}]`, this._appendCopyright(filename, content));
97+
this._appendFormInput(form, `files[${filename}]`, this._appendCopyright(filename, content));
8998
}
9099

91100
/**
@@ -94,18 +103,20 @@ export class PlunkerWriter {
94103
* This will replace those placeholders with the names from the example metadata,
95104
* e.g. "<basic-button-example>" and "BasicButtonExample"
96105
*/
97-
_replaceExamplePlaceholderNames(fileName: string, fileContent: string): string {
106+
_replaceExamplePlaceholderNames(data: ExampleData,
107+
fileName: string,
108+
fileContent: string): string {
98109
if (fileName == 'index.html') {
99110
// Replace the component selector in `index,html`.
100-
// For example, <material-docs-example></material-docs-exmaple> will be replaced as
111+
// For example, <material-docs-example></material-docs-example> will be replaced as
101112
// <button-demo></button-demo>
102-
fileContent = fileContent.replace(/material-docs-example/g, this.exampleData.selectorName);
113+
fileContent = fileContent.replace(/material-docs-example/g, data.selectorName);
103114
} else if (fileName == 'main.ts') {
104115
// Replace the component name in `main.ts`.
105116
// For example, `import {MaterialDocsExample} from 'material-docs-example'`
106117
// will be replaced as `import {ButtonDemo} from './button-demo'`
107-
fileContent = fileContent.replace(/MaterialDocsExample/g, this.exampleData.componentName);
108-
fileContent = fileContent.replace(/material-docs-example/g, this.exampleData.indexFilename);
118+
fileContent = fileContent.replace(/MaterialDocsExample/g, data.componentName);
119+
fileContent = fileContent.replace(/material-docs-example/g, data.indexFilename);
109120
}
110121
return fileContent;
111122
}

0 commit comments

Comments
 (0)
Please sign in to comment.