@soleil-se/app-util 3.0.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -19
- package/MIGRATION.md +136 -0
- package/README.md +146 -266
- package/client/attribute-util/getCurrentScript.js +56 -0
- package/{attribute-util → client/attribute-util}/index.js +4 -1
- package/{svelte-client → client/svelte}/index.js +4 -8
- package/{render-vue → client/vue}/index.js +7 -8
- package/common/index.js +144 -0
- package/common/legacy/getRouteUri.js +19 -0
- package/common/legacy/getViewUri.js +21 -0
- package/docs/1.render.md +53 -0
- package/docs/2.svelte.md +52 -0
- package/docs/3.vue.md +19 -0
- package/docs/4.underscore.md +48 -0
- package/package.json +6 -8
- package/server/index.js +78 -0
- package/server/svelte/index.js +33 -0
- package/app-data/.eslintrc +0 -4
- package/app-data/index.js +0 -16
- package/attribute-util/.eslintrc +0 -4
- package/src/index.js +0 -162
- package/svelte-client/.eslintrc +0 -4
- package/svelte-server/index.js +0 -26
- /package/{render-vue → client}/.eslintrc +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Scoped version of https://github.com/amiller-gh/currentScript-polyfill */
|
|
2
|
+
// document.currentScript polyfill by Adam Miller
|
|
3
|
+
// MIT license
|
|
4
|
+
|
|
5
|
+
export default function getCurrentScript() {
|
|
6
|
+
if (!('currentScript' in document)) {
|
|
7
|
+
// IE 8-10 support script readyState
|
|
8
|
+
// IE 11+ support stack trace
|
|
9
|
+
try {
|
|
10
|
+
throw new Error();
|
|
11
|
+
} catch (err) {
|
|
12
|
+
// Find the second match for the "at" string to get file src url from stack.
|
|
13
|
+
// Specifically works with the format of stack traces in IE.
|
|
14
|
+
let i = 0;
|
|
15
|
+
const stackDetails = (/.*at [^(]*\((.*):(.+):(.+)\)$/ig).exec(err.stack);
|
|
16
|
+
const scriptLocation = (stackDetails && stackDetails[1]) || false;
|
|
17
|
+
const line = (stackDetails && stackDetails[2]) || false;
|
|
18
|
+
const currentLocation = document.location.href.replace(document.location.hash, '');
|
|
19
|
+
let pageSource;
|
|
20
|
+
let inlineScriptSourceRegExp;
|
|
21
|
+
let inlineScriptSource;
|
|
22
|
+
const scripts = document.getElementsByTagName('script'); // Live NodeList collection
|
|
23
|
+
|
|
24
|
+
if (scriptLocation === currentLocation) {
|
|
25
|
+
pageSource = document.documentElement.outerHTML;
|
|
26
|
+
inlineScriptSourceRegExp = new RegExp(`(?:[^\\n]+?\\n){0,${line - 2}}[^<]*<script>([\\d\\D]*?)<\\/script>[\\d\\D]*`, 'i');
|
|
27
|
+
inlineScriptSource = pageSource.replace(inlineScriptSourceRegExp, '$1').trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (; i < scripts.length; i += 1) {
|
|
31
|
+
// If ready state is interactive, return the script tag
|
|
32
|
+
if (scripts[i].readyState === 'interactive') {
|
|
33
|
+
return scripts[i];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If src matches, return the script tag
|
|
37
|
+
if (scripts[i].src === scriptLocation) {
|
|
38
|
+
return scripts[i];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If inline source matches, return the script tag
|
|
42
|
+
if (
|
|
43
|
+
scriptLocation === currentLocation
|
|
44
|
+
&& scripts[i].innerHTML
|
|
45
|
+
&& scripts[i].innerHTML.trim() === inlineScriptSource
|
|
46
|
+
) {
|
|
47
|
+
return scripts[i];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If no match, return null
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return document.currentScript;
|
|
56
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import getCurrentScript from './getCurrentScript';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* JSON decode an attribute on the currentScript element.
|
|
3
5
|
* Use if attribute contains a JSON-object.
|
|
@@ -5,7 +7,8 @@
|
|
|
5
7
|
* @returns {Object} Decoded JSON object.
|
|
6
8
|
*/
|
|
7
9
|
export const decodeAttribute = (attribute) => {
|
|
8
|
-
const
|
|
10
|
+
const currentScript = getCurrentScript();
|
|
11
|
+
const encoded = currentScript.getAttribute(attribute);
|
|
9
12
|
if (encoded) {
|
|
10
13
|
return decodeURIComponent(encoded);
|
|
11
14
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import '
|
|
3
|
-
|
|
4
|
-
import { decodeAttribute, parseAttribute } from '../attribute-util';
|
|
5
|
-
import { setAppData } from '../app-data';
|
|
6
|
-
|
|
7
|
-
const props = parseAttribute('data-app');
|
|
8
|
-
setAppData(props);
|
|
2
|
+
import { decodeAttribute } from '../attribute-util';
|
|
3
|
+
import { getAppData } from '../../common';
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
|
-
*
|
|
6
|
+
* Renders a Svelte application
|
|
12
7
|
* @param {Svelte} App Svelte application to be started.
|
|
13
8
|
*/
|
|
14
9
|
export function render(App) {
|
|
@@ -16,6 +11,7 @@ export function render(App) {
|
|
|
16
11
|
|
|
17
12
|
const mountElement = document.querySelector(selector);
|
|
18
13
|
const hydrate = mountElement.childElementCount > 0;
|
|
14
|
+
const props = getAppData();
|
|
19
15
|
if (mountElement) {
|
|
20
16
|
return new App({
|
|
21
17
|
hydrate,
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
/* eslint-disable-next-line import/no-unresolved */
|
|
2
3
|
import Vue from 'vue';
|
|
3
4
|
|
|
4
|
-
import { decodeAttribute
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const options = parseAttribute('data-app');
|
|
8
|
-
setAppData(options);
|
|
5
|
+
import { decodeAttribute } from '../attribute-util';
|
|
6
|
+
import { getAppData, isOffline } from '../../common';
|
|
9
7
|
|
|
10
8
|
const offlineModeMixin = {
|
|
11
9
|
mounted() {
|
|
12
|
-
if (
|
|
10
|
+
if (isOffline) {
|
|
13
11
|
const $anchors = this.$el.querySelectorAll('a');
|
|
14
12
|
$anchors.forEach(($anchor) => {
|
|
15
13
|
$anchor.addEventListener('click', (e) => {
|
|
@@ -24,8 +22,9 @@ const offlineModeMixin = {
|
|
|
24
22
|
* Render a Vue application
|
|
25
23
|
* @param {Vue} App Vue application to be started.
|
|
26
24
|
*/
|
|
27
|
-
export
|
|
25
|
+
export function render(App) {
|
|
28
26
|
const selector = decodeAttribute('data-selector');
|
|
27
|
+
const options = getAppData();
|
|
29
28
|
|
|
30
29
|
Object.assign(App, options, {
|
|
31
30
|
mixins: [offlineModeMixin],
|
package/common/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
import { parseAttribute } from '../client/attribute-util';
|
|
3
|
+
import getLegacyRouteUri from './legacy/getRouteUri';
|
|
4
|
+
import getLegacyViewUri from './legacy/getViewUri';
|
|
5
|
+
|
|
6
|
+
const appMetadata = process.browser ? parseAttribute('data-metadata') : {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get an ID for the app.
|
|
10
|
+
* @return {String} ID
|
|
11
|
+
*/
|
|
12
|
+
const getAppId = () => {
|
|
13
|
+
const PortletContextUtil = require('PortletContextUtil');
|
|
14
|
+
const id = (PortletContextUtil.getPortletNamespace('') || 'ADDON_PREVIEW').replace('.', '_');
|
|
15
|
+
const decoratedNode = PortletContextUtil.getCurrentDecoratedNode();
|
|
16
|
+
|
|
17
|
+
if (decoratedNode) {
|
|
18
|
+
return `${decoratedNode.getIdentifier().replace('.', '_')}_${id}`;
|
|
19
|
+
}
|
|
20
|
+
return id;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Regex for selecting leading slashes
|
|
25
|
+
* @constant {Regex}
|
|
26
|
+
*/
|
|
27
|
+
const leadingSlashes = /^(\/)*/g;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Regex for selecting trailing slashes
|
|
31
|
+
* @constant {Regex}
|
|
32
|
+
*/
|
|
33
|
+
const trailingSlashes = /(\/)*$/g;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* DOM friendly unique identifier for the WebApp.
|
|
37
|
+
* @constant {String}
|
|
38
|
+
*/
|
|
39
|
+
export const appId = process.server ? getAppId() : appMetadata.id;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* If the WebApp is running in offline mode or not.
|
|
43
|
+
* @constant {Boolean}
|
|
44
|
+
*/
|
|
45
|
+
export const isOffline = process.server
|
|
46
|
+
? require('VersionUtil').getCurrentVersion() === require('VersionUtil').OFFLINE_VERSION
|
|
47
|
+
: appMetadata.isOffline;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* If the WebApp is running in online mode or not.
|
|
51
|
+
* @constant {Boolean}
|
|
52
|
+
*/
|
|
53
|
+
export const isOnline = !isOffline;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get a prefixed namespace unique for app.
|
|
57
|
+
* @param {string} [prefix='app']
|
|
58
|
+
* @return {String} - Prefixed namespace.
|
|
59
|
+
*/
|
|
60
|
+
export function getNamespace(prefix = 'app') {
|
|
61
|
+
return `${prefix}_${appId}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get URI for a route, same as `getStandaloneUrl` in SiteVision template.
|
|
66
|
+
* @param {String} route A route.
|
|
67
|
+
* @returns {String} URI for route.
|
|
68
|
+
*/
|
|
69
|
+
export function getRouteUri(route = '') {
|
|
70
|
+
const path = route.replace(leadingSlashes, '');
|
|
71
|
+
if (process.server) {
|
|
72
|
+
const router = require('router');
|
|
73
|
+
if (router.getStandaloneUrl) {
|
|
74
|
+
return router
|
|
75
|
+
.getStandaloneUrl(path !== '' ? `/${path}` : path)
|
|
76
|
+
.replace(trailingSlashes, '');
|
|
77
|
+
}
|
|
78
|
+
// If SiteVision 7 or older.
|
|
79
|
+
return getLegacyRouteUri(path);
|
|
80
|
+
}
|
|
81
|
+
return `${appMetadata.baseRouteUri}/${path}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get URI for a view, same as `getUrl` in SiteVision template.
|
|
86
|
+
* @param {String} route A route.
|
|
87
|
+
* @returns {String} URI for view.
|
|
88
|
+
*/
|
|
89
|
+
export function getViewUri(route = '') {
|
|
90
|
+
if (process.server) {
|
|
91
|
+
const router = require('router');
|
|
92
|
+
if (router.getUrl) {
|
|
93
|
+
return router.getUrl(route);
|
|
94
|
+
}
|
|
95
|
+
// If SiteVision 7 or older.
|
|
96
|
+
return getLegacyViewUri(route, { isOffline });
|
|
97
|
+
}
|
|
98
|
+
return appMetadata.baseViewUri + route;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get URI for a resource.
|
|
103
|
+
* @param {String} resource A resource.
|
|
104
|
+
* @returns {String} URI for a resource.
|
|
105
|
+
*/
|
|
106
|
+
export function getResourceUri(resource = '') {
|
|
107
|
+
const path = resource.replace(leadingSlashes, '');
|
|
108
|
+
if (process.server) {
|
|
109
|
+
const appInfo = require('appInfo');
|
|
110
|
+
return `/webapp-files/${appInfo.appIdentifier}/${appInfo.appVersion}/${path}`
|
|
111
|
+
.replace(trailingSlashes, '');
|
|
112
|
+
}
|
|
113
|
+
return `${appMetadata.baseResourceUri}/${path}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getAppMetadata() {
|
|
117
|
+
return {
|
|
118
|
+
id: appId,
|
|
119
|
+
isOffline,
|
|
120
|
+
isOnline,
|
|
121
|
+
baseRouteUri: getRouteUri(),
|
|
122
|
+
baseViewUri: getViewUri(),
|
|
123
|
+
baseResourceUri: getResourceUri(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let appData = process.browser ? parseAttribute('data-app-data') : {};
|
|
128
|
+
|
|
129
|
+
export function setAppData(data) {
|
|
130
|
+
appData = data;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get appData value or object that is passed to app when rendering.
|
|
135
|
+
* @export
|
|
136
|
+
* @param {String} [key] - Key for value.
|
|
137
|
+
* @return {*|Object} - Value or object.
|
|
138
|
+
*/
|
|
139
|
+
export function getAppData(key) {
|
|
140
|
+
if (process.browser && !Object.keys(appData).length) {
|
|
141
|
+
setAppData(parseAttribute('data-options'));
|
|
142
|
+
}
|
|
143
|
+
return key ? appData[key] : appData;
|
|
144
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Regex for selecting trailing slashes
|
|
5
|
+
* @constant {Regex}
|
|
6
|
+
*/
|
|
7
|
+
const trailingSlashes = /(\/)*$/g;
|
|
8
|
+
|
|
9
|
+
export default function getRouteUri(route) {
|
|
10
|
+
const PortletContextUtil = require('PortletContextUtil');
|
|
11
|
+
const currentPage = PortletContextUtil.getCurrentPage();
|
|
12
|
+
const currentPortlet = PortletContextUtil.getCurrentPortlet();
|
|
13
|
+
if (currentPage && currentPortlet) {
|
|
14
|
+
const currentPageId = currentPage.getIdentifier().replace('_sitePage', '');
|
|
15
|
+
const currentPortletId = currentPortlet.getIdentifier();
|
|
16
|
+
return `/appresource/${currentPageId}/${currentPortletId}/${route}`.replace(trailingSlashes, '');
|
|
17
|
+
}
|
|
18
|
+
return '/';
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
|
|
3
|
+
export default function getViewUri(route, { isOffline }) {
|
|
4
|
+
const PortletContextUtil = require('PortletContextUtil');
|
|
5
|
+
const PropertyUtil = require('PropertyUtil');
|
|
6
|
+
const appInfo = require('appInfo');
|
|
7
|
+
const currentPage = PortletContextUtil.getCurrentPage();
|
|
8
|
+
const currentPortlet = PortletContextUtil.getCurrentPortlet();
|
|
9
|
+
|
|
10
|
+
if (currentPage && currentPortlet) {
|
|
11
|
+
const currentPageId = currentPage.getIdentifier().replace('_sitePage', '');
|
|
12
|
+
const currentPageUri = PropertyUtil.getString(currentPage, 'URI');
|
|
13
|
+
const currentPortletId = PortletContextUtil.getCurrentPortlet().getIdentifier();
|
|
14
|
+
if (isOffline) {
|
|
15
|
+
return `/edit-offline/${currentPageId}?sv.target=${currentPortletId}&sv.${currentPortletId}.route=${encodeURI(route)}`;
|
|
16
|
+
}
|
|
17
|
+
return `${currentPageUri}?sv.target=${currentPortletId}&sv.${currentPortletId}.route=${encodeURI(route)}`;
|
|
18
|
+
}
|
|
19
|
+
const addonId = appInfo['jcr:uuid'];
|
|
20
|
+
return `/edit-web-app-offline/${addonId}?sv.target=${addonId}&sv.${addonId}.route=${encodeURI(route)}`;
|
|
21
|
+
}
|
package/docs/1.render.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Render
|
|
2
|
+
Returns HTML for a script tag with server data avaliable as attributes on the tag.
|
|
3
|
+
Framework independent
|
|
4
|
+
|
|
5
|
+
## index.js
|
|
6
|
+
### render([data], [settings]) ⇒ <code>String</code>
|
|
7
|
+
`@soleil-api/webapp-util/server`
|
|
8
|
+
|
|
9
|
+
Get a HTML string for rendering an application.
|
|
10
|
+
|
|
11
|
+
**Returns**: <code>String</code> - HTML for rendering an application.
|
|
12
|
+
|
|
13
|
+
| Param | Type | Default | Description |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| [data] | <code>Object</code> | <code>{}</code> | Server data that will be available in the attribute. `data-app-data` on the script tag. |
|
|
16
|
+
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
|
|
17
|
+
| [settings.html] | <code>String</code> | <code>''</code> | HTML that will be rendered inside mount element. |
|
|
18
|
+
| [settings.selector] | <code>String</code> | <code>`[data-portlet-id="${portletId}"]`</code> | Query selector for where the app should be mounted. |
|
|
19
|
+
| [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
|
|
20
|
+
| [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
|
|
21
|
+
| [settings.req] | <code>Object</code> | | The req object from SiteVision, pass this to optimize browser specific script loading if you have multiple instances of the app. |
|
|
22
|
+
|
|
23
|
+
[Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
|
|
24
|
+
|
|
25
|
+
### Example
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import router from 'router';
|
|
29
|
+
import { render } from '@soleil-api/webapp-util/server';
|
|
30
|
+
|
|
31
|
+
router.get('/', (req, res) => {
|
|
32
|
+
const data = { foo: 'bar' };
|
|
33
|
+
res.send(render(data));
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**All settings**
|
|
38
|
+
```js
|
|
39
|
+
import router from 'router';
|
|
40
|
+
import { render } from '@soleil-api/webapp-util/server';
|
|
41
|
+
|
|
42
|
+
router.get('/', (req, res) => {
|
|
43
|
+
const data = { foo: 'bar' };
|
|
44
|
+
const settings = {
|
|
45
|
+
html: '<noscript>You can put a noscript message here for example!</noscript>',
|
|
46
|
+
selector: '#mount_me_here',
|
|
47
|
+
async: false,
|
|
48
|
+
defer: true,
|
|
49
|
+
req,
|
|
50
|
+
}
|
|
51
|
+
res.send(render(data, settings));
|
|
52
|
+
};
|
|
53
|
+
```
|
package/docs/2.svelte.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Svelte
|
|
2
|
+
SiteVision supports both server and client side rendering with Svelte.
|
|
3
|
+
|
|
4
|
+
## index.js
|
|
5
|
+
For rendering a client side only app use the framework independent [client renderer](./1.render.md).
|
|
6
|
+
|
|
7
|
+
### `render(App, [props], [settings])` ⇒ `String`
|
|
8
|
+
`@soleil-api/webapp-util/server/svelte`
|
|
9
|
+
|
|
10
|
+
Get a HTML string for rendering a universal Svelte application.
|
|
11
|
+
If a client bundle is available the server rendered HTML will be hydrated and not completely re-rendered.
|
|
12
|
+
|
|
13
|
+
**Returns**: <code>String</code> - HTML for rendering a Svelte application.
|
|
14
|
+
|
|
15
|
+
| Param | Type | Default | Description |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| [props] | <code>Object</code> | <code>{}</code> | Server data that will be available as props and as app data. |
|
|
18
|
+
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. Forwarded to |
|
|
19
|
+
| [settings.selector] | <code>String</code> | <code>`[data-portlet-id="${portletId}"]`</code> | Query selector for where the app should be mounted. |
|
|
20
|
+
| [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
|
|
21
|
+
| [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
|
|
22
|
+
| [settings.req] | <code>Object</code> | | The req object from SiteVision, pass this to optimize browser specific script loading if you have multiple instances of the app. |
|
|
23
|
+
|
|
24
|
+
[Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
|
|
25
|
+
|
|
26
|
+
In `app_src/index.js` or `app_src/server/index.js`.
|
|
27
|
+
```javascript
|
|
28
|
+
import router from 'router';
|
|
29
|
+
import { render } from '@soleil-api/webapp-util/server/svelte';
|
|
30
|
+
|
|
31
|
+
import App from './App.svelte';
|
|
32
|
+
|
|
33
|
+
router.get('/', (req, res) => {
|
|
34
|
+
const props = { foo: 'bar' };
|
|
35
|
+
res.send(render(App, data));
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## main.js
|
|
40
|
+
|
|
41
|
+
### `render(App)`
|
|
42
|
+
`@soleil-api/webapp-util/client/svelte`
|
|
43
|
+
|
|
44
|
+
Renders the client side app.
|
|
45
|
+
|
|
46
|
+
In `app_src/main.js` or `app_src/client/index.js`.
|
|
47
|
+
```javascript
|
|
48
|
+
import { render } from '@soleil-api/webapp-util/client/svelte';
|
|
49
|
+
import App from './App.svelte';
|
|
50
|
+
|
|
51
|
+
render(App);
|
|
52
|
+
```
|
package/docs/3.vue.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Vue
|
|
2
|
+
SiteVision supports client side rendering with Vue.
|
|
3
|
+
|
|
4
|
+
## index.js
|
|
5
|
+
For rendering a client side only app use the framework independent [client renderer](./1.render.md).
|
|
6
|
+
|
|
7
|
+
## main.js
|
|
8
|
+
### `render(App)`
|
|
9
|
+
`@soleil-api/webapp-util/client/vue`
|
|
10
|
+
|
|
11
|
+
Renders the client side app.
|
|
12
|
+
|
|
13
|
+
In `app_src/main.js` or `app_src/client/index.js`.
|
|
14
|
+
```javascript
|
|
15
|
+
import { render } from '@soleil-api/webapp-util/client/vue';
|
|
16
|
+
import App from './App.vue';
|
|
17
|
+
|
|
18
|
+
render(App);
|
|
19
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Underscore
|
|
2
|
+
Render a Underscore template.
|
|
3
|
+
|
|
4
|
+
## `renderTemplate(template, [values])` ⇒ `String`
|
|
5
|
+
Renders a Underscore template and returns a string.
|
|
6
|
+
This function is also available inside the template.
|
|
7
|
+
|
|
8
|
+
**Returns**: `String` - Rendered template
|
|
9
|
+
|
|
10
|
+
| Param | Type | Default | Description |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| template | `String` | | Underscore template. |
|
|
13
|
+
| [values] | `Object` | `{}` | Values. |
|
|
14
|
+
|
|
15
|
+
### Examples
|
|
16
|
+
**Simple example**
|
|
17
|
+
```javascript
|
|
18
|
+
import { renderTemplate } from '@soleil-api/webapp-util/server';
|
|
19
|
+
|
|
20
|
+
const string = renderTemplate('<div><%= foo %></div>', {
|
|
21
|
+
foo: 'bar',
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
**Multiple templates**
|
|
25
|
+
```html
|
|
26
|
+
/* views/item.html */
|
|
27
|
+
<li>
|
|
28
|
+
<%- name %>
|
|
29
|
+
</li>
|
|
30
|
+
```
|
|
31
|
+
```html
|
|
32
|
+
/* views/main.html */
|
|
33
|
+
<ul>
|
|
34
|
+
<% items.forEach(function(item) { %>
|
|
35
|
+
<%= renderTemplate(itemTemplate, item) %>
|
|
36
|
+
<% }); %>
|
|
37
|
+
</ul>
|
|
38
|
+
```
|
|
39
|
+
```javascript
|
|
40
|
+
import { renderTemplate } from '@soleil-api/webapp-util/server';
|
|
41
|
+
import mainTemplate from './views/main.html';
|
|
42
|
+
import itemTemplate from './views/item.html';
|
|
43
|
+
|
|
44
|
+
const items = [{ name: 'Foo' }, { name: 'Bar' }, { name: 'Baz' }];
|
|
45
|
+
const string = renderTemplate(mainTemplate, { items, itemTemplate });
|
|
46
|
+
```
|
|
47
|
+
> **NOTE**
|
|
48
|
+
> Remember that the second argument must be an object and that objects properties are accessed directly in any child templates!
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soleil-se/app-util",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Utility functions for Webapps.",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./common/index.js",
|
|
6
6
|
"author": "Soleil AB",
|
|
7
7
|
"contributors": [
|
|
8
8
|
"Kimmy Monassar",
|
|
@@ -10,13 +10,11 @@
|
|
|
10
10
|
],
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"private": false,
|
|
13
|
-
"homepage": "https://
|
|
14
|
-
"
|
|
15
|
-
"current-script-polyfill": "^1.0.0"
|
|
16
|
-
},
|
|
17
|
-
"peerDependencies": {
|
|
13
|
+
"homepage": "https://docs.soleilit.se/03.packages/@soleil-api&app-util",
|
|
14
|
+
"optionalDependencies": {
|
|
18
15
|
"vue": "^2.6.11"
|
|
19
16
|
},
|
|
20
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "a59289650f6f1c5277566106c29fb8db01fbebd1",
|
|
18
|
+
"dependencies": {},
|
|
21
19
|
"devDependencies": {}
|
|
22
20
|
}
|
package/server/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { appImportDate } from 'appInfo';
|
|
2
|
+
/* Underscore is provided by SiteVision */
|
|
3
|
+
/* eslint-disable-next-line import/no-extraneous-dependencies */
|
|
4
|
+
import _ from 'underscore';
|
|
5
|
+
import {
|
|
6
|
+
appId, isOffline, getResourceUri, getAppMetadata,
|
|
7
|
+
} from '../common';
|
|
8
|
+
|
|
9
|
+
const isIE = (req) => {
|
|
10
|
+
if (!req) return true;
|
|
11
|
+
const userAgent = req.header('user-agent');
|
|
12
|
+
return /Trident\/|MSIE/.test(userAgent);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get a HTML string for rendering an application.
|
|
17
|
+
* @param {Object} [data={}] Server data that will be available in the attribute.
|
|
18
|
+
* `data-app-data` on the script tag.
|
|
19
|
+
* @param {Object} [settings={}] Settings object.
|
|
20
|
+
* @param {String} [settings.html=''] HTML that will be rendered inside mount element.
|
|
21
|
+
* @param {String} [settings.selector=`[data-portlet-id="${portletId}"]`] Query selector for
|
|
22
|
+
* where the app should be mounted.
|
|
23
|
+
* @param {Boolean} [settings.async=false] If the app script should be loaded asynchronously.
|
|
24
|
+
* [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
|
|
25
|
+
* @param {Boolean} [settings.defer=true] If the app script should be loaded after DOM is ready.
|
|
26
|
+
* [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
|
|
27
|
+
* @param {Object} [settings.req] The req object from SiteVision, pass this to optimize browser
|
|
28
|
+
* specific script loading if you have multiple instances of the app.
|
|
29
|
+
* @returns {String} HTML for rendering an application.
|
|
30
|
+
*/
|
|
31
|
+
export function render(data, {
|
|
32
|
+
html = '',
|
|
33
|
+
selector = `[data-portlet-id="${appId}"]`,
|
|
34
|
+
async = false,
|
|
35
|
+
defer = true,
|
|
36
|
+
req,
|
|
37
|
+
} = {}) {
|
|
38
|
+
const appMetadata = getAppMetadata();
|
|
39
|
+
|
|
40
|
+
if (isOffline) {
|
|
41
|
+
return `
|
|
42
|
+
<div data-portlet-id="${appId}">${html}</div>
|
|
43
|
+
<script>
|
|
44
|
+
window.svDocReady(function() {
|
|
45
|
+
var targetElement = document.querySelector('[data-portlet-id="${appId}"]');
|
|
46
|
+
var script = document.createElement("script");
|
|
47
|
+
script.src = "${getResourceUri('client/index.js')}?${appImportDate}${appId}";
|
|
48
|
+
script.setAttribute("data-metadata", "${encodeURIComponent(JSON.stringify(appMetadata))}");
|
|
49
|
+
script.setAttribute("data-app-data", "${encodeURIComponent(JSON.stringify(data))}");
|
|
50
|
+
script.setAttribute("data-selector", "${encodeURIComponent(selector)}");
|
|
51
|
+
targetElement.parentNode.insertBefore(script, targetElement.nextSibling);
|
|
52
|
+
});
|
|
53
|
+
</script>`;
|
|
54
|
+
}
|
|
55
|
+
return `
|
|
56
|
+
<div data-portlet-id="${appId}">${html}</div>
|
|
57
|
+
<script
|
|
58
|
+
src="${getResourceUri('client/index.js')}?${appImportDate}${isIE(req) ? appId : ''}"
|
|
59
|
+
data-metadata="${encodeURIComponent(JSON.stringify(appMetadata))}"
|
|
60
|
+
data-app-data="${encodeURIComponent(JSON.stringify(data))}"
|
|
61
|
+
data-selector="${encodeURIComponent(selector)}"
|
|
62
|
+
${async && !defer ? 'async' : ''}
|
|
63
|
+
${defer ? 'defer' : ''}>
|
|
64
|
+
</script>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Renders a Underscore template and returns a string.
|
|
69
|
+
* @param {String} template Underscore template.
|
|
70
|
+
* @param {Object} [values={}] Values.
|
|
71
|
+
* @returns {String} Rendered template
|
|
72
|
+
*/
|
|
73
|
+
export function renderTemplate(template, values = {}) {
|
|
74
|
+
if (typeof template === 'function') {
|
|
75
|
+
return template({ ...values, renderTemplate });
|
|
76
|
+
}
|
|
77
|
+
return _.template(template)(({ ...values, renderTemplate }));
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
import appResource from 'appResource';
|
|
3
|
+
import { setAppData } from '../../common';
|
|
4
|
+
import { render as renderClient } from '../index';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SSR Svelte App.
|
|
8
|
+
* @export
|
|
9
|
+
* @param {SvelteComponent} App - Svelte component that is root of app.
|
|
10
|
+
* @param {Object} [props] - Properties available on root component. Also forwarded as appData.
|
|
11
|
+
* @return {String} - HTML for rendering a Svelte application.
|
|
12
|
+
*/
|
|
13
|
+
export function renderServer(App, props = {}) {
|
|
14
|
+
setAppData(props);
|
|
15
|
+
const { html } = App.render(props);
|
|
16
|
+
return html;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render a Svelte App.
|
|
21
|
+
* @export
|
|
22
|
+
* @param {SvelteComponent} App - Svelte component that is root of app.
|
|
23
|
+
* @param {Object} [props] - Properties available on root component. Also forwarded as appData.
|
|
24
|
+
* @param {Object} [settings] - Settings forwarded to render function for client app.
|
|
25
|
+
* @return {String} - HTML for rendering a Svelte application.
|
|
26
|
+
*/
|
|
27
|
+
export function render(App, props = {}, settings) {
|
|
28
|
+
const html = renderServer(App, props);
|
|
29
|
+
if (appResource.getNode('client/index.js')) {
|
|
30
|
+
return renderClient(props, { ...settings, html });
|
|
31
|
+
}
|
|
32
|
+
return html;
|
|
33
|
+
}
|
package/app-data/.eslintrc
DELETED
package/app-data/index.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { parseAttribute } from '../attribute-util';
|
|
2
|
-
|
|
3
|
-
let appData = {};
|
|
4
|
-
|
|
5
|
-
export function setAppData(data) {
|
|
6
|
-
appData = data;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function getAppData(key) {
|
|
10
|
-
if (!Object.keys(appData).length && process && process.browser) {
|
|
11
|
-
setAppData(parseAttribute('data-app'));
|
|
12
|
-
}
|
|
13
|
-
return key ? appData[key] : appData;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default process && process.browser ? parseAttribute('data-app') : appData;
|
package/attribute-util/.eslintrc
DELETED