Skip to content

Commit b561291

Browse files
committed
Fix eclipsesource#566: Improve styling
1 parent e7b85f2 commit b561291

File tree

9 files changed

+299
-17
lines changed

9 files changed

+299
-17
lines changed

example/example.ts

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import * as _ from 'lodash';
2+
import {JsonFormsHolder} from '../src/core';
13
import {JsonSchema} from '../src/models/jsonSchema';
24
import {UISchemaElement} from '../src/models/uischema';
35
import {JsonForms} from '../src/json-forms';
6+
import {Style} from '../src/core/styling.registry';
7+
8+
declare let $;
49
declare let exampleDivId;
510
declare let viewDivId;
611
export interface ExampleDescription {
@@ -44,7 +49,7 @@ const changeExample = (selectedExample: string) => {
4449

4550
body.appendChild(jsonForms);
4651
};
47-
const crateExampleSelection = () => {
52+
const createExampleSelection = (): HTMLSelectElement => {
4853
const examplesDiv = document.getElementById(exampleDivId);
4954
const labelExample = document.createElement('label');
5055
labelExample.textContent = 'Example:';
@@ -57,34 +62,124 @@ const crateExampleSelection = () => {
5762
const option = document.createElement('option');
5863
option.value = example.name;
5964
option.label = example.label;
65+
option.text = example.label;
6066
select.appendChild(option);
6167
});
6268
select.onchange = () => (changeExample(select.value));
6369
examplesDiv.appendChild(select);
6470
changeExample(select.value);
65-
}
66-
const changeStyle = (style: string) => {
71+
return select;
72+
};
73+
74+
const changeTheme = (style: string) => {
6775
document.body.className = style;
6876
};
69-
const createStyleSelection = () => {
70-
const examplesDiv = document.getElementById('examples');
71-
const labelStyle = document.createElement('label');
72-
labelStyle.textContent = 'Style:';
73-
labelStyle.htmlFor = 'example_style';
74-
examplesDiv.appendChild(labelStyle);
77+
78+
const createThemeSelection = () => {
79+
const themeDiv = document.getElementById('theme');
80+
7581
const select = document.createElement('select');
76-
select.id = 'example_style';
82+
select.id = 'example_theme';
7783
Object.keys(knownStyles).forEach(key => {
7884
const style = knownStyles[key];
7985
const option = document.createElement('option');
8086
option.value = key;
8187
option.label = style;
88+
option.text = style;
8289
select.appendChild(option);
8390
});
84-
select.onchange = (ev: Event) => (changeStyle(select.value));
85-
examplesDiv.appendChild(select);
91+
select.onchange = (ev: Event) => (changeTheme(select.value));
92+
93+
const themeLabel = document.createElement('label');
94+
themeLabel.textContent = 'Theme:';
95+
themeLabel.htmlFor = 'example_theme';
96+
97+
themeDiv.appendChild(themeLabel);
98+
themeDiv.appendChild(select);
99+
};
100+
101+
function createStyleSelection(selectExampleElement: HTMLSelectElement) {
102+
const styleDiv = document.getElementById('style');
103+
// create select element for selecting style to be applied
104+
const selectStyle = document.createElement('select');
105+
['none', 'bootstrap', 'materialize'].forEach(style => {
106+
const option = document.createElement('option');
107+
option.value = style;
108+
option.label = _.capitalize(style);
109+
option.text = style;
110+
selectStyle.appendChild(option);
111+
});
112+
selectStyle.onchange = () => {
113+
changeStyle(selectStyle.value);
114+
// re-render the easy way
115+
const currentExample = selectExampleElement.value;
116+
changeExample(currentExample);
117+
};
118+
const styleLabel = document.createElement('label');
119+
styleLabel.innerText = 'Style';
120+
styleDiv.appendChild(styleLabel);
121+
styleDiv.appendChild(selectStyle);
122+
123+
changeStyle('none');
86124
}
125+
87126
window.onload = (ev) => {
88-
crateExampleSelection();
89-
createStyleSelection();
127+
const selectExampleElement = createExampleSelection();
128+
createThemeSelection();
129+
createStyleSelection(selectExampleElement);
90130
};
131+
132+
function changeStyle(style) {
133+
$('select').material_select('destroy');
134+
if (style === 'bootstrap') {
135+
bootstrap();
136+
} else if (style === 'materialize') {
137+
material();
138+
} else {
139+
none();
140+
}
141+
}
142+
143+
function none() {
144+
enableLink('example')
145+
}
146+
147+
function bootstrap() {
148+
enableLink('bootstrap');
149+
JsonFormsHolder.stylingRegistry.registerMany([
150+
{
151+
name: 'button',
152+
classNames: ['btn', 'btn-primary']
153+
},
154+
{
155+
name: 'select',
156+
classNames: ['custom-select']
157+
},
158+
]);
159+
$('select').attr('class', JsonFormsHolder.stylingRegistry.getAsClassName('select'));
160+
}
161+
162+
function material() {
163+
enableLink('materialize');
164+
JsonFormsHolder.stylingRegistry.register(
165+
'button',
166+
['btn', 'waves-effect', 'waves-light']
167+
);
168+
JsonFormsHolder.stylingRegistry.unregister('select');
169+
$('select').material_select();
170+
}
171+
172+
/**
173+
* Disables all links and only enables the one containing the wanted href.
174+
* This function assumes that all links are CSS links used for styling purposes.
175+
*
176+
* @param wantedHref a substring of the link's href value, which is to be enabled
177+
*/
178+
function enableLink(wantedHref: string): void {
179+
const links = $('link').toArray();
180+
// disable all links
181+
_.forEach(links, link => link.disabled = true);
182+
const wantedLink = _.find(links, (link: HTMLLinkElement) => link.href.includes(wantedHref));
183+
// enable wanted link
184+
wantedLink.disabled = false;
185+
}

example/templates/uischema-registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ const tester = (test_uischema, test_data) => 5;
2525
const setup = (div: HTMLDivElement) => {
2626
const registerButton = document.createElement('button');
2727
registerButton.innerText = 'Register UI Schema';
28+
registerButton.className = JsonFormsHolder.stylingRegistry.getAsClassName('button');
2829
registerButton.onclick = () => {
2930
JsonFormsHolder.uischemaRegistry.register(uischema, tester);
3031
resetServices();
3132
};
3233
div.appendChild(registerButton);
3334
const unregisterButton = document.createElement('button');
35+
unregisterButton.className = JsonFormsHolder.stylingRegistry.getAsClassName('button');
3436
unregisterButton.innerText = 'Unregister UI Schema';
3537
unregisterButton.onclick = () => {
3638
JsonFormsHolder.uischemaRegistry.unregister(uischema, tester);

example_plain/index.html

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,35 @@
77

88
<meta name="viewport" content="width=device-width, initial-scale=1">
99
<script src="assets/native-shim.js"></script>
10-
<script src="assets/bundle.js"></script>
10+
11+
<!-- JQuery is needed for both, Bootstrap 4 as well as materialize -->
12+
<script src="assets/jquery.js"></script>
13+
14+
<!-- Materialize CSS -->
15+
<link rel="stylesheet" href="assets/materialize.css">
16+
17+
<!-- Bootstrap 4 CSS -->
18+
<link rel="stylesheet" href="assets/bootstrap.css">
19+
20+
<!-- Plain CSS -->
1121
<link rel="stylesheet" href="assets/example.css">
22+
23+
<!-- Bootstrap JS, if we need it, it must be loaded before materialize JS, otherwise the select elements seem to break -->
24+
<!--<script src="assets/bootstrap.js"></script>-->
25+
26+
<!-- materialize JS -->
27+
<script src="assets/materialize.js"></script>
28+
<script src="assets/bundle.js"></script>
1229
</head>
1330
<body>
14-
<div id="examples"></div>
31+
<div id="root">
32+
<div id="toolbar">
33+
<div id="examples"></div>
34+
<div id="theme"></div>
35+
<div id="style"></div>
36+
</div>
1537
<div id="view"></div>
38+
</div>
1639
</body>
1740
<script>
1841
exampleDivId = 'examples';

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@
3939
"@types/gulp": "3.8.28",
4040
"ava": "^0.18.1",
4141
"awesome-typescript-loader": "^3.0.0-beta.18",
42+
"bootstrap": "^4.0.0-alpha.6",
4243
"copy-webpack-plugin": "^4.0.1",
4344
"coveralls": "^2.11.16",
4445
"document-register-element": "^1.4.1",
4546
"file-loader": "^0.10.0",
47+
"jquery": "^3.2.1",
4648
"jsdom": "^9.11.0",
4749
"jsdom-global": "2.1.1",
4850
"lite-server": "^2.3.0",
51+
"materialize-css": "^0.98.2",
4952
"ncp": "^2.0.0",
5053
"nyc": "^10.1.2",
5154
"source-map-loader": "^0.1.6",

src/core.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {JsonSchema} from './models/jsonSchema';
33
import {UiSchemaRegistry, UiSchemaRegistryImpl} from './core/uischema.registry';
44
import {DataService} from './core/data.service';
55
import {RendererService} from './core/renderer.service';
6+
import {StylingRegistry, StylingRegistryImpl} from './core/styling.registry';
67

78
export interface JsonFormService {
89
dispose(): void;
@@ -17,4 +18,5 @@ export class JsonFormsHolder {
1718
public static rendererService = new RendererService();
1819
public static jsonFormsServices: Array<JsonFormsServiceConstructable> = [];
1920
public static uischemaRegistry: UiSchemaRegistry = new UiSchemaRegistryImpl();
21+
public static stylingRegistry: StylingRegistry = new StylingRegistryImpl();
2022
}

src/core/styling.registry.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as _ from 'lodash';
2+
3+
export interface Style {
4+
name: string
5+
classNames: Array<string>
6+
}
7+
8+
export interface StylingRegistry {
9+
/**
10+
* Register a style.
11+
* If a style with the given name already exists, it will be overwritten.
12+
*
13+
* @param styleName the name of the style
14+
* @param classNames CSS class names to be applied
15+
*/
16+
register(styleName: string, classNames: Array<string>): void;
17+
18+
/**
19+
* Register a style.
20+
* If a style with the given name already exists, it will be overwritten.
21+
*
22+
* @param style the style to be registered
23+
*/
24+
register(style: Style): void;
25+
26+
/**
27+
* Register multiple styles at once.
28+
*
29+
* @param styles an array of styles to be registered
30+
*/
31+
registerMany(styles: Array<Style>): void;
32+
33+
/**
34+
* Un-register a style.
35+
*
36+
* @param styleName the name of the style to be un-registered
37+
*/
38+
unregister(styleName: string): void;
39+
40+
/**
41+
* Obtain the CSS class name associated with the given style name.
42+
* @param styleName the name whose CSS class names should be obtained
43+
* @return an array containing the CSS class names, if the style exists, an empty array otherwise
44+
*/
45+
get(styleName: string): Array<string>
46+
47+
/**
48+
* Obtain the CSS class name associated with the given style name.
49+
* @param styleName the name whose CSS class names should be obtained
50+
* @return a string containing the CSS class name separated by whitespace, if the style exists,
51+
* empty string otherwise
52+
*/
53+
getAsClassName(styleName: string): string
54+
}
55+
56+
export class StylingRegistryImpl implements StylingRegistry {
57+
58+
constructor(protected styles: Array<Style> = []) {
59+
60+
}
61+
62+
register(style: Style): void;
63+
register(name: string, classNames: string[]): void;
64+
register(style: string|Style, classNames?: string[]): void {
65+
if (typeof style === 'string') {
66+
this.unregister(style);
67+
this.styles.push({name: style, classNames});
68+
} else {
69+
this.unregister(style.name);
70+
this.styles.push(style);
71+
}
72+
}
73+
74+
registerMany(styles: Array<Style>) {
75+
styles.forEach(style => this.register(style.name, style.classNames))
76+
}
77+
78+
unregister(styleName: any) {
79+
_.remove(this.styles, style => style.name === styleName);
80+
}
81+
82+
get(styleName: string): string[] {
83+
const foundStyle = _.find(this.styles, style => style.name === styleName);
84+
if (foundStyle) {
85+
return foundStyle.classNames;
86+
}
87+
return [];
88+
}
89+
90+
getAsClassName(styleName: string): string {
91+
const styles = this.get(styleName);
92+
if (_.isEmpty(styles)) {
93+
return '';
94+
}
95+
return _.join(styles, ' ');
96+
}
97+
}

src/renderers/additional/array-renderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { resolveSchema } from '../../path.util';
88
import { JsonSchema } from '../../models/jsonSchema';
99
import { getElementLabelObject } from '../label.util';
1010
import { RankedTester, rankWith, and, uiTypeIs, schemaMatches } from '../../core/testers';
11+
import { JsonFormsHolder } from '../../core';
1112

1213
export const arrayTester: RankedTester = rankWith(2, and(
1314
uiTypeIs('Control'),
@@ -85,6 +86,7 @@ export class ArrayControlRenderer extends Renderer implements DataChangeListener
8586
div.appendChild(content);
8687

8788
const button = document.createElement('button');
89+
button.className = JsonFormsHolder.stylingRegistry.getAsClassName('button');
8890
button.textContent = `Add to ${labelObject.text}`;
8991
button.onclick = (ev: Event) => {
9092
if (arrayData === undefined) {

0 commit comments

Comments
 (0)