@soleil-se/app-util 2.4.0 → 3.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 CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.0.0] - 2020-09-28
8
+ ### Added
9
+ - Svelte support.
10
+ - New exports:
11
+ - `uniqueId` - A unique id for the app instance.
12
+
13
+ ## [2.4.1] - 2020-09-01
14
+ ### Fixed
15
+ - App data with single quotes crashes app in edit mode, use double quotes instead.
16
+
7
17
  ## [2.4.0] - 2020-05-26
8
18
  ### Fixed
9
19
  - `getViewUri` returns correct URI when in offline mode and in the Addons view.
package/README.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Webapp Util
2
2
  Utility functions for Webapps.
3
+
4
+ <!-- TOC -->
5
+
6
+ - [Install](#install)
7
+ - [Changelog](#changelog)
8
+ - [Migration 1.x.x to 2.x.x](#migration-1xx-to-2xx)
9
+ - [Usage](#usage)
10
+ - [`renderApp([config], [settings])` ⇒ `String`](#renderappconfig-settings-⇒-string)
11
+ - [Example](#example)
12
+ - [**Vue**](#vue)
13
+ - [`getRouteUri(route)` ⇒ `String`](#getrouteuriroute-⇒-string)
14
+ - [Example](#example-1)
15
+ - [`getViewUri(route)` ⇒ `String`](#getviewuriroute-⇒-string)
16
+ - [Example](#example-2)
17
+ - [`getResourceUri(resource)` ⇒ `String`](#getresourceuriresource-⇒-string)
18
+ - [Example](#example-3)
19
+ - [`renderTemplate(template, [values])` ⇒ `String`](#rendertemplatetemplate-values-⇒-string)
20
+ - [Example](#example-4)
21
+ - [`isOffline`](#isoffline)
22
+ - [`isOnline`](#isonline)
23
+ - [`uniqueId`](#uniqueid)
24
+ - [AppData](#appdata)
25
+ - [Svelte](#svelte)
26
+ - [Server](#server)
27
+ - [`render(App, [data], [settings])` ⇒ `String`](#renderapp-data-settings-⇒-string)
28
+ - [`renderServer(App, [data])` ⇒ `String`](#renderserverapp-data-⇒-string)
29
+ - [`renderClient(App, [data], [settings])` ⇒ `String`](#renderclientapp-data-settings-⇒-string)
30
+ - [Client](#client)
31
+ - [`render(App)`](#renderapp)
32
+
33
+ <!-- /TOC -->
34
+
3
35
  ## Install
4
36
  `yarn add @soleil-se/webapp-util`
5
37
 
@@ -20,8 +52,8 @@ See [example](#example).
20
52
  import { renderApp, getRouteUri, getViewUri, getResourceUri, renderTemplate, isOffline, isOnline } from '@soleil-se/webapp-util';
21
53
  ```
22
54
  ### `renderApp([config], [settings])` ⇒ `String`
23
- Get HTML string for rendering an application.
24
- Can be used together with `@soleil-se/webapp-util/render-vue` or a custom render function in the client code.
55
+ Get HTML string for rendering a client side application.
56
+ Can be used together with `@soleil-se/webapp-util/render-vue`, `@soleil-se/webapp-util/svelte-client` or a custom render function in the client code.
25
57
 
26
58
  **Returns**: <code>String</code> - HTML for rendering an application.
27
59
 
@@ -54,7 +86,7 @@ router.get('/', (req, res) => {
54
86
  });
55
87
  ```
56
88
  ##### **Vue**
57
- In `app_src/client/index.js`.
89
+ In `app_src/client/index.js` or `app_src/main.js`.
58
90
  ```javascript
59
91
  import render from '@soleil-se/webapp-util/render-vue';
60
92
  import App from './App.vue';
@@ -62,24 +94,6 @@ import App from './App.vue';
62
94
  render(App);
63
95
  ```
64
96
 
65
- ##### **App data**
66
- If you need to use the data that is available for the App you can use `@soleil-se/webapp-util/app-data`.
67
-
68
- For example when you need a route URI from the server:
69
- ```javascript
70
- import AppData from '@soleil-se/webapp-util/app-data';
71
- import superagent from 'superagent';
72
-
73
- const searchThings = async (query) => {
74
- const { body } = await superagent
75
- .get(AppData.searchRoute)
76
- .query({ query });
77
- return body;
78
- };
79
-
80
- export default searchThings;
81
- ```
82
-
83
97
  ### `getRouteUri(route)` ⇒ `String`
84
98
  Get URI for a route, same as `getStandaloneUrl` in SiteVision template.
85
99
  https://developer.sitevision.se/docs/webapps/template#h-Methods
@@ -169,7 +183,8 @@ const string = renderTemplate(mainTemplate, { items, itemTemplate });
169
183
  > **NOTE**
170
184
  > Remember that the second argument must be an object and that objects properties are accessed directly in any child templates!
171
185
  ### `isOffline`
172
- If the webapp is running in offline mode or not.
186
+ If the webapp is running in offline mode or not.
187
+ This value is also passed to AppData.
173
188
 
174
189
  ```javascript
175
190
  if(isOffline) {
@@ -178,10 +193,137 @@ if(isOffline) {
178
193
  ```
179
194
 
180
195
  ### `isOnline`
181
- If the webapp is running in online mode or not.
196
+ If the webapp is running in online mode or not.
197
+ This value is also passed to AppData.
182
198
 
183
199
  ```javascript
184
200
  if(isOnline) {
185
201
  // Do something
186
202
  }
187
203
  ```
204
+
205
+ ### `uniqueId`
206
+ A unique ID for the WebApp.
207
+ This value is also passed to AppData.
208
+
209
+ ## AppData
210
+ `@soleil-se/webapp-util/app-data`
211
+ Data that is available for the WebApp.
212
+ ```javascript
213
+ import AppData from '@soleil-se/webapp-util/app-data';
214
+
215
+ // These are avaliable per default:
216
+ const { isOffline, isOnline, uniqueId } = AppData;
217
+ ```
218
+
219
+ All data passed to the app is available in AppData:
220
+ index.js
221
+ ```javascript
222
+ res.send(renderApp({ foo: 'bar' }))
223
+ ```
224
+ In app:
225
+ ```javascript
226
+ import AppData from '@soleil-se/webapp-util/app-data';
227
+
228
+ console.log(AppData.foo);
229
+
230
+ // Or descructured
231
+ const { foo } = AppData;
232
+ console.log(foo);
233
+ ```
234
+
235
+ ## Svelte
236
+ Functions for rendering a Svelte application.
237
+
238
+ ### Server
239
+ `@soleil-se/webapp-util/svelte-server`
240
+ #### `render(App, [data], [settings])` ⇒ `String`
241
+ Get HTML string for rendering a universal Svelte application.
242
+ To be used together with `@soleil-se/webapp-util/svelte-client`.
243
+
244
+ **Returns**: <code>String</code> - HTML for rendering an application.
245
+
246
+ | Param | Type | Default | Description |
247
+ | --- | --- | --- | --- |
248
+ | [App] | <code>Svelte</code> | | Svelte application. |
249
+ | [data] | <code>Object</code> | <code>{}</code> | Server data or config that will be available from `@soleil-se/webapp-util/app-data` |
250
+ | [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
251
+ | [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/) |
252
+ | [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/) |
253
+ | [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. |
254
+
255
+ In `app_src/server/index.js` or `app_src/index.js`.
256
+ ```javascript
257
+ import router from 'router';
258
+ import { render } from '@soleil-se/webapp-util/svelte-server';
259
+
260
+ import App from './App.svelte';
261
+
262
+ router.get('/', (req, res) => {
263
+ const data = { foo: 'bar' };
264
+ res.send(renderSvelte(App, data));
265
+ });
266
+ ```
267
+
268
+ #### `renderServer(App, [data])` ⇒ `String`
269
+ Get HTML string for a server only rendered Svelte application.
270
+
271
+ **Returns**: <code>String</code> - HTML for rendering an application.
272
+
273
+ | Param | Type | Default | Description |
274
+ | --- | --- | --- | --- |
275
+ | [App] | <code>Svelte</code> | | Svelte application. |
276
+ | [data] | <code>Object</code> | <code>{}</code> | Server data or config that will be available from `@soleil-se/webapp-util/app-data` |
277
+
278
+ In `app_src/server/index.js` or `app_src/index.js`.
279
+ ```javascript
280
+ import router from 'router';
281
+ import { renderServer } from '@soleil-se/webapp-util/svelte-server';
282
+
283
+ import App from './App.svelte';
284
+
285
+ router.get('/', (req, res) => {
286
+ const data = { foo: 'bar' };
287
+ res.send(renderServer(App, data));
288
+ });
289
+ ```
290
+
291
+ #### `renderClient(App, [data], [settings])` ⇒ `String`
292
+ Get HTML string for a client only rendered Svelte application.
293
+ Can be used together with `@soleil-se/webapp-util/svelte-client`.
294
+
295
+ **Returns**: <code>String</code> - HTML for rendering an application.
296
+
297
+ | Param | Type | Default | Description |
298
+ | --- | --- | --- | --- |
299
+ | [App] | <code>Svelte</code> | | Svelte application. |
300
+ | [data] | <code>Object</code> | <code>{}</code> | Server data or config that will be available from `@soleil-se/webapp-util/app-data` |
301
+ | [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
302
+ | [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/) |
303
+ | [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/) |
304
+ | [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. |
305
+
306
+ In `app_src/server/index.js` or `app_src/index.js`.
307
+ ```javascript
308
+ import router from 'router';
309
+ import { renderClient } from '@soleil-se/webapp-util/svelte-server';
310
+
311
+ import App from './App.svelte';
312
+
313
+ router.get('/', (req, res) => {
314
+ const data = { foo: 'bar' };
315
+ res.send(renderSvelte(App, data));
316
+ });
317
+ ```
318
+
319
+ ### Client
320
+ `@soleil-se/webapp-util/svelte-client`
321
+ #### `render(App)`
322
+ In `app_src/client/index.js` or `app_src/main.js`.
323
+ ```javascript
324
+ import { render } from '@soleil-se/webapp-util/svelte-client';
325
+ import App from './App.svelte';
326
+
327
+ render(App);
328
+ ```
329
+
package/app-data/index.js CHANGED
@@ -1,7 +1,16 @@
1
1
  import { parseAttribute } from '../attribute-util';
2
2
 
3
- /**
4
- * Get appData from the currentScript element.
5
- * @returns {Object} AppData.
6
- */
7
- export default parseAttribute('data-app');
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;
@@ -1,4 +1,3 @@
1
- const { currentScript } = document;
2
1
  /**
3
2
  * JSON decode an attribute on the currentScript element.
4
3
  * Use if attribute contains a JSON-object.
@@ -6,7 +5,7 @@ const { currentScript } = document;
6
5
  * @returns {Object} Decoded JSON object.
7
6
  */
8
7
  export const decodeAttribute = (attribute) => {
9
- const encoded = currentScript.getAttribute(attribute);
8
+ const encoded = document.currentScript.getAttribute(attribute);
10
9
  if (encoded) {
11
10
  return decodeURIComponent(encoded);
12
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleil-se/app-util",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "Utility functions for Webapps.",
5
5
  "main": "./src/index.js",
6
6
  "author": "Soleil AB",
@@ -10,11 +10,11 @@
10
10
  ],
11
11
  "license": "UNLICENSED",
12
12
  "private": false,
13
- "homepage": "https://github.com/soleilit/server-monorepo/tree/master/packages/app-util",
13
+ "homepage": "https://github.com/soleilit/server-monorepo/tree/app-util/next/packages/webapp-util#readme",
14
14
  "dependencies": {
15
15
  "current-script-polyfill": "^1.0.0"
16
16
  },
17
- "optionalDependencies": {
17
+ "peerDependencies": {
18
18
  "vue": "^2.6.11"
19
19
  },
20
20
  "devDependencies": {}
@@ -1,8 +1,11 @@
1
1
  import 'current-script-polyfill';
2
2
  import Vue from 'vue';
3
3
 
4
- import { decodeAttribute } from '../attribute-util';
5
- import appData from '../app-data';
4
+ import { decodeAttribute, parseAttribute } from '../attribute-util';
5
+ import { setAppData } from '../app-data';
6
+
7
+ const options = parseAttribute('data-app');
8
+ setAppData(options);
6
9
 
7
10
  const offlineModeMixin = {
8
11
  mounted() {
@@ -23,7 +26,6 @@ const offlineModeMixin = {
23
26
  */
24
27
  export default function render(App) {
25
28
  const selector = decodeAttribute('data-selector');
26
- const options = appData;
27
29
 
28
30
  Object.assign(App, options, {
29
31
  mixins: [offlineModeMixin],
package/src/index.js CHANGED
@@ -13,7 +13,19 @@ export const isOffline = VersionUtil.getCurrentVersion() === VersionUtil.OFFLINE
13
13
  export const isOnline = !isOffline;
14
14
 
15
15
  const currentPortlet = PortletContextUtil.getCurrentPortlet();
16
- const portletId = currentPortlet ? currentPortlet.getIdentifier().replace('.', '_') : '';
16
+
17
+ const getUniqueId = () => {
18
+ const id = (currentPortlet ? currentPortlet.getIdentifier() : appInfo['jcr:uuid']).replace('.', '_');
19
+ const decoratedNode = PortletContextUtil.getCurrentDecoratedNode();
20
+
21
+ if (decoratedNode) {
22
+ return `${decoratedNode.getIdentifier().replace('.', '_')}_${id}`;
23
+ }
24
+ return id;
25
+ };
26
+
27
+
28
+ export const uniqueId = getUniqueId();
17
29
 
18
30
  /**
19
31
  * Get URI for a resource.
@@ -39,7 +51,8 @@ export function renderTemplate(template, values = {}) {
39
51
 
40
52
  /**
41
53
  * Get a HTML string for rendering an application.
42
- * @param {Object} [data={}] Server config or data that will be available in the attribute
54
+ * @param {Svelte} [App] Svelte application.
55
+ * @param {Object} [data={}] Server config or data that will be available in the attribute.
43
56
  * `data-app` on currentScript. In Vue it will also be available in the `$options` object.
44
57
  * @param {Object} [settings={}] Settings object.
45
58
  * @param {String} [settings.noScript=''] HTML that will be rendered when JavaScript
@@ -56,7 +69,8 @@ export function renderTemplate(template, values = {}) {
56
69
  */
57
70
  export function renderApp(data, {
58
71
  noScript = '',
59
- selector = `[data-portlet-id="${portletId}"]`,
72
+ html = '',
73
+ selector = `[data-portlet-id="${uniqueId}"]`,
60
74
  async = false,
61
75
  defer = true,
62
76
  req,
@@ -71,25 +85,31 @@ export function renderApp(data, {
71
85
  return /Trident\/|MSIE/.test(userAgent);
72
86
  };
73
87
 
88
+ const getHtml = () => (html || (noScript ? `<noscript>${noScript}</noscript>` : ''));
89
+
90
+ const appData = {
91
+ ...data, isOffline, isOnline, uniqueId,
92
+ };
93
+
74
94
  if (isOffline) {
75
95
  return `
76
- <div data-portlet-id="${portletId}">${noScript}</div>
96
+ <div data-portlet-id="${uniqueId}">${getHtml()}</div>
77
97
  <script>
78
98
  window.svDocReady(function() {
79
- var targetElement = document.querySelector('[data-portlet-id="${portletId}"]');
80
- var script = document.createElement('script');
81
- script.src = '${getResourceUri('client/index.js')}?${appInfo.appImportDate}${portletId.replace('_', '')}';
82
- script.setAttribute('data-app', '${encodeURIComponent(JSON.stringify({ ...data, isOffline }))}');
83
- script.setAttribute('data-selector', '${encodeURIComponent(selector)}');
99
+ var targetElement = document.querySelector('[data-portlet-id="${uniqueId}"]');
100
+ var script = document.createElement("script");
101
+ script.src = "${getResourceUri('client/index.js')}?${appInfo.appImportDate}${uniqueId.replace('_', '')}";
102
+ script.setAttribute("data-app", "${encodeURIComponent(JSON.stringify(appData))}");
103
+ script.setAttribute("data-selector", "${encodeURIComponent(selector)}");
84
104
  targetElement.parentNode.insertBefore(script, targetElement.nextSibling);
85
105
  });
86
106
  </script>`;
87
107
  }
88
108
  return `
89
- <div data-portlet-id="${portletId}"><noscript>${noScript}</noscript></div>
109
+ <div data-portlet-id="${uniqueId}">${getHtml()}</div>
90
110
  <script
91
- src="${getResourceUri('client/index.js')}?${appInfo.appImportDate}${isIE() ? portletId.replace('_', '') : ''}"
92
- data-app="${encodeURIComponent(JSON.stringify({ ...data, isOffline }))}"
111
+ src="${getResourceUri('client/index.js')}?${appInfo.appImportDate}${isIE() ? uniqueId.replace('_', '') : ''}"
112
+ data-app="${encodeURIComponent(JSON.stringify(appData))}"
93
113
  data-selector="${encodeURIComponent(selector)}"
94
114
  ${async && !defer ? 'async' : ''}
95
115
  ${defer ? 'defer' : ''}>
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "@soleil/eslint-config-sitevision/client",
3
+ "root": true
4
+ }
@@ -0,0 +1,31 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+ import 'current-script-polyfill';
3
+
4
+ import { decodeAttribute, parseAttribute } from '../attribute-util';
5
+ import { setAppData } from '../app-data';
6
+
7
+ const props = parseAttribute('data-app');
8
+ setAppData(props);
9
+
10
+ /**
11
+ * Render a Svelte application
12
+ * @param {Svelte} App Svelte application to be started.
13
+ */
14
+ export function render(App) {
15
+ const selector = decodeAttribute('data-selector');
16
+
17
+ const mountElement = document.querySelector(selector);
18
+ const hydrate = mountElement.childElementCount > 0;
19
+ if (mountElement) {
20
+ return new App({
21
+ hydrate,
22
+ target: document.querySelector(selector),
23
+ props,
24
+ });
25
+ }
26
+ return document.addEventListener('DOMContentLoaded', () => new App({
27
+ hydrate,
28
+ target: document.querySelector(selector),
29
+ props,
30
+ }));
31
+ }
@@ -0,0 +1,26 @@
1
+ import appResource from 'appResource';
2
+ import { setAppData } from '../app-data';
3
+ import {
4
+ renderApp, isOffline, isOnline, uniqueId,
5
+ } from '../src';
6
+
7
+ export function renderClient(data, settings) {
8
+ return renderApp(data, settings);
9
+ }
10
+
11
+ export function renderServer(App, data) {
12
+ const appData = {
13
+ ...data, isOffline, isOnline, uniqueId,
14
+ };
15
+ setAppData(appData);
16
+ const { html } = App.render(appData);
17
+ return html;
18
+ }
19
+
20
+ export function render(App, data, settings) {
21
+ const html = renderServer(App, data);
22
+ if (appResource.getNode('client/index.js')) {
23
+ return renderClient(data, { html, ...settings });
24
+ }
25
+ return html;
26
+ }