@soleil-se/app-util 4.0.0 → 4.1.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,14 @@ 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
+ ## [4.1.0] - 2021-02-17
8
+ ### Changed
9
+ - Put app data and metadata in script elements instead of data attributes.
10
+
11
+ ## [4.0.1] - 2021-01-28
12
+ ### Fixed
13
+ - Crashes when no data is passed to app and then trying to call `getAppData`.
14
+
7
15
  ## [4.0.0] - 2021-01-27
8
16
  A much needed major overhaul of the package.
9
17
  See [MIGRATION](./MIGRATION.md).
@@ -12,6 +20,7 @@ See [MIGRATION](./MIGRATION.md).
12
20
  - Major refactoring of package.
13
21
  - Exports for render functions are moved.
14
22
  - All documented constants and functions from base import (`@soleil-api/webapp-util`) now works both in a server and client context.
23
+ - Settings for selector is moved from server to client rendering functions.
15
24
 
16
25
  ### Removed
17
26
  - `noScript` option in `render` (formerly `renderApp`) has been removed. If a no script message is needed use the `html` option wrapped in `<noscript>`.
package/MIGRATION.md CHANGED
@@ -5,6 +5,8 @@ Migration from version 2 or 3 to 4.
5
5
  <!-- TOC -->
6
6
 
7
7
  - [renderApp](#renderapp)
8
+ - [Export](#export)
9
+ - [Settings](#settings)
8
10
  - [renderTemplate](#rendertemplate)
9
11
  - [getRouteUri](#getrouteuri)
10
12
  - [AppData](#appdata)
@@ -18,6 +20,7 @@ Migration from version 2 or 3 to 4.
18
20
  <!-- /TOC -->
19
21
 
20
22
  ## renderApp
23
+ ### Export
21
24
  Export moved and renamed.
22
25
 
23
26
  Old:
@@ -28,6 +31,23 @@ New:
28
31
  ```js
29
32
  import { render } from '@soleil-api/webapp-util/server';
30
33
  ```
34
+
35
+ ### Settings
36
+ Selector is moved to client side rendering functions.
37
+
38
+ Old (index.js):
39
+ ```js
40
+ res.send(renderApp(data, { selector: '#mount_app_here' }));
41
+ ```
42
+
43
+ New (main.js):
44
+ ```js
45
+ import { render } from '@soleil-api/webapp-util/client/vue';
46
+ import App from './App.vue';
47
+
48
+ render(App, { selector: '#mount_app_here' });
49
+ ```
50
+
31
51
  ## renderTemplate
32
52
  Export moved.
33
53
 
@@ -1,27 +1,26 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { decodeAttribute } from '../attribute-util';
3
- import { getAppData } from '../../common';
2
+ import { appId, getAppData } from '../../common';
4
3
 
5
4
  /**
6
- * Renders a Svelte application
7
- * @param {Svelte} App Svelte application to be started.
5
+ * Renders a client side Svelte application.
6
+ * @param {*} App Svelte app root component.
7
+ * @param {Object} [settings={}] Settings object.
8
+ * @param {String} [settings.selector=`#app_mount_${appId}`] Query selector for mount element.
9
+ * @param {String} [settings.intro=false] If true, will play transitions on initial render,
10
+ * rather than waiting for subsequent state changes.
11
+ * @return {*} Initialized Svelte component.
8
12
  */
9
- export function render(App) {
10
- const selector = decodeAttribute('data-selector');
11
-
12
- const mountElement = document.querySelector(selector);
13
- const hydrate = mountElement.childElementCount > 0;
13
+ export function render(App, {
14
+ selector = `#app_mount_${appId}`,
15
+ intro = false,
16
+ } = {}) {
17
+ const target = document.querySelector(selector);
18
+ const hydrate = target.childElementCount > 0;
14
19
  const props = getAppData();
15
- if (mountElement) {
16
- return new App({
17
- hydrate,
18
- target: document.querySelector(selector),
19
- props,
20
- });
21
- }
22
- return document.addEventListener('DOMContentLoaded', () => new App({
20
+ return new App({
23
21
  hydrate,
24
- target: document.querySelector(selector),
22
+ target,
25
23
  props,
26
- }));
24
+ intro,
25
+ });
27
26
  }
@@ -6,7 +6,7 @@ import getCurrentScript from './getCurrentScript';
6
6
  * @param {String} attribute Attribute to decode.
7
7
  * @returns {Object} Decoded JSON object.
8
8
  */
9
- export const decodeAttribute = (attribute) => {
9
+ export const getAttribute = (attribute) => {
10
10
  const currentScript = getCurrentScript();
11
11
  const encoded = currentScript.getAttribute(attribute);
12
12
  if (encoded) {
@@ -15,10 +15,7 @@ export const decodeAttribute = (attribute) => {
15
15
  return undefined;
16
16
  };
17
17
 
18
- /**
19
- * Parse an attribute on the currentScript element.
20
- * Us if the attribute is a String.
21
- * @param {String} attribute
22
- * @returns {String} Parsed attribute value.
23
- */
24
- export const parseAttribute = (attribute) => JSON.parse(decodeAttribute(attribute));
18
+ export const parseJson = (type) => {
19
+ const id = getAttribute('data-app-id');
20
+ return JSON.parse(document.getElementById(`${type}_${id}`)?.textContent || '{}');
21
+ };
@@ -1,42 +1,32 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- /* eslint-disable-next-line import/no-unresolved */
2
+ /* eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies */
3
3
  import Vue from 'vue';
4
4
 
5
- import { decodeAttribute } from '../attribute-util';
6
- import { getAppData, isOffline } from '../../common';
5
+ import { appId, getAppData, isOffline } from '../../common';
7
6
 
8
7
  const offlineModeMixin = {
9
8
  mounted() {
10
- if (isOffline) {
11
- const $anchors = this.$el.querySelectorAll('a');
12
- $anchors.forEach(($anchor) => {
13
- $anchor.addEventListener('click', (e) => {
14
- e.preventDefault();
15
- window.$svjq($anchor).trigger('click');
16
- }, false);
17
- });
18
- }
9
+ const $anchors = this.$el.querySelectorAll('a');
10
+ $anchors.forEach(($anchor) => {
11
+ $anchor.addEventListener('click', (e) => {
12
+ e.preventDefault();
13
+ window.$svjq($anchor).trigger('click');
14
+ }, false);
15
+ });
19
16
  },
20
17
  };
21
18
  /**
22
- * Render a Vue application
23
- * @param {Vue} App Vue application to be started.
19
+ * Renders a client side Vue application.
20
+ * @param {*} App Vue app root component.
21
+ * @param {Object} [settings={}] Settings object.
22
+ * @param {String} [settings.selector=`#app_mount_${appId}`] Query selector for mount element.
24
23
  */
25
- export function render(App) {
26
- const selector = decodeAttribute('data-selector');
24
+ export function render(App, { selector = `#app_mount_${appId}` } = {}) {
27
25
  const options = getAppData();
28
26
 
29
27
  Object.assign(App, options, {
30
- mixins: [offlineModeMixin],
28
+ mixins: isOffline ? [offlineModeMixin] : [],
31
29
  });
32
30
 
33
- const mountElementExists = !!document.querySelector(selector);
34
-
35
- if (mountElementExists) {
36
- new Vue(App).$mount(selector);
37
- } else {
38
- document.addEventListener('DOMContentLoaded', () => {
39
- new Vue(App).$mount(selector);
40
- });
41
- }
31
+ new Vue(App).$mount(selector);
42
32
  }
package/common/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable global-require */
2
- import { parseAttribute } from '../client/attribute-util';
2
+ import { parseJson } from '../client/utils';
3
3
  import getLegacyRouteUri from './legacy/getRouteUri';
4
4
  import getLegacyViewUri from './legacy/getViewUri';
5
5
 
6
- const appMetadata = process.browser ? parseAttribute('data-metadata') : {};
6
+ const appMetadata = process.browser ? parseJson('app_meta') : {};
7
7
 
8
8
  /**
9
9
  * Get an ID for the app.
@@ -124,7 +124,7 @@ export function getAppMetadata() {
124
124
  };
125
125
  }
126
126
 
127
- let appData = process.browser ? parseAttribute('data-app-data') : {};
127
+ let appData = process.browser ? parseJson('app_data') : undefined;
128
128
 
129
129
  export function setAppData(data) {
130
130
  appData = data;
@@ -137,8 +137,8 @@ export function setAppData(data) {
137
137
  * @return {*|Object} - Value or object.
138
138
  */
139
139
  export function getAppData(key) {
140
- if (process.browser && !Object.keys(appData).length) {
141
- setAppData(parseAttribute('data-options'));
140
+ if (process.browser && !appData) {
141
+ setAppData(parseJson('app_data'));
142
142
  }
143
143
  return key ? appData[key] : appData;
144
144
  }
package/docs/1.render.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Render
2
2
  Returns HTML for a script tag with server data avaliable as attributes on the tag.
3
- Framework independent
3
+ Framework agnostic.
4
4
 
5
5
  ## index.js
6
6
  ### render([data], [settings]) ⇒ <code>String</code>
@@ -15,7 +15,6 @@ Get a HTML string for rendering an application.
15
15
  | [data] | <code>Object</code> | <code>{}</code> | Server data that will be available in the attribute. `data-app-data` on the script tag. |
16
16
  | [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
17
17
  | [settings.html] | <code>String</code> | <code>&#x27;&#x27;</code> | HTML that will be rendered inside mount element. |
18
- | [settings.selector] | <code>String</code> | <code>&#x60;[data-portlet-id&#x3D;&quot;${portletId}&quot;]&#x60;</code> | Query selector for where the app should be mounted. |
19
18
  | [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
20
19
  | [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
21
20
  | [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. |
@@ -42,8 +41,7 @@ import { render } from '@soleil-api/webapp-util/server';
42
41
  router.get('/', (req, res) => {
43
42
  const data = { foo: 'bar' };
44
43
  const settings = {
45
- html: '<noscript>You can put a noscript message here for example!</noscript>',
46
- selector: '#mount_me_here',
44
+ html: '<noscript>You can put a noscript message here for example!</noscript>',
47
45
  async: false,
48
46
  defer: true,
49
47
  req,
package/docs/2.svelte.md CHANGED
@@ -2,9 +2,9 @@
2
2
  SiteVision supports both server and client side rendering with Svelte.
3
3
 
4
4
  ## index.js
5
- For rendering a client side only app use the framework independent [client renderer](./1.render.md).
5
+ For rendering a client side only app use the framework agnostic [client renderer](./1.render.md).
6
6
 
7
- ### `render(App, [props], [settings])` ⇒ `String`
7
+ ### `render(App, [props], [settings])`
8
8
  `@soleil-api/webapp-util/server/svelte`
9
9
 
10
10
  Get a HTML string for rendering a universal Svelte application.
@@ -16,7 +16,6 @@ If a client bundle is available the server rendered HTML will be hydrated and no
16
16
  | --- | --- | --- | --- |
17
17
  | [props] | <code>Object</code> | <code>{}</code> | Server data that will be available as props and as app data. |
18
18
  | [settings] | <code>Object</code> | <code>{}</code> | Settings object. Forwarded to |
19
- | [settings.selector] | <code>String</code> | <code>&#x60;[data-portlet-id&#x3D;&quot;${portletId}&quot;]&#x60;</code> | Query selector for where the app should be mounted. |
20
19
  | [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
21
20
  | [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
22
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. |
@@ -38,10 +37,20 @@ router.get('/', (req, res) => {
38
37
 
39
38
  ## main.js
40
39
 
41
- ### `render(App)`
40
+ ### `render(App, [settings])`
42
41
  `@soleil-api/webapp-util/client/svelte`
43
42
 
44
- Renders the client side app.
43
+ Renders a client side Svelte application.
44
+
45
+ **Returns**: <code>\*</code> - Initialized Svelte component.
46
+
47
+ | Param | Type | Default | Description |
48
+ | --- | --- | --- | --- |
49
+ | App | <code>\*</code> | | Svelte app root component. |
50
+ | [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
51
+ | [settings.selector] | <code>String</code> | <code>&#x60;#app_mount_${appId}&#x60;</code> | Query selector for mount element. |
52
+ | [settings.intro] | <code>String</code> | <code>false</code> | If true, will play transitions on initial render, rather than waiting for subsequent state changes. |
53
+
45
54
 
46
55
  In `app_src/main.js` or `app_src/client/index.js`.
47
56
  ```javascript
@@ -50,3 +59,11 @@ import App from './App.svelte';
50
59
 
51
60
  render(App);
52
61
  ```
62
+
63
+ Mount the app in another element:
64
+ ```javascript
65
+ import { render } from '@soleil-api/webapp-util/client/svelte';
66
+ import App from './App.svelte';
67
+
68
+ render(App, { selector: '#mount_app_here' });
69
+ ```
package/docs/3.vue.md CHANGED
@@ -2,13 +2,19 @@
2
2
  SiteVision supports client side rendering with Vue.
3
3
 
4
4
  ## index.js
5
- For rendering a client side only app use the framework independent [client renderer](./1.render.md).
5
+ For rendering a client side only app use the framework agnostic [client renderer](./1.render.md).
6
6
 
7
7
  ## main.js
8
- ### `render(App)`
8
+
9
+ ### `render(App, [settings])`
9
10
  `@soleil-api/webapp-util/client/vue`
11
+ Renders a client side Vue application.
10
12
 
11
- Renders the client side app.
13
+ | Param | Type | Default | Description |
14
+ | --- | --- | --- | --- |
15
+ | App | <code>\*</code> | | Vue app root component. |
16
+ | [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
17
+ | [settings.selector] | <code>String</code> | <code>&#x60;#app_mount_${appId}&#x60;</code> | Query selector for mount element. |
12
18
 
13
19
  In `app_src/main.js` or `app_src/client/index.js`.
14
20
  ```javascript
@@ -17,3 +23,12 @@ import App from './App.vue';
17
23
 
18
24
  render(App);
19
25
  ```
26
+
27
+ Mount the app in another element:
28
+ ```javascript
29
+ import { render } from '@soleil-api/webapp-util/client/vue';
30
+ import App from './App.vue';
31
+
32
+ render(App, { selector: '#mount_app_here' });
33
+ ```
34
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleil-se/app-util",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Utility functions for Webapps.",
5
5
  "main": "./common/index.js",
6
6
  "author": "Soleil AB",
@@ -14,7 +14,7 @@
14
14
  "optionalDependencies": {
15
15
  "vue": "^2.6.11"
16
16
  },
17
- "gitHead": "a59289650f6f1c5277566106c29fb8db01fbebd1",
17
+ "gitHead": "5248563f9ecdec86866cb5cd361990c5ff8626a5",
18
18
  "dependencies": {},
19
19
  "devDependencies": {}
20
20
  }
package/server/index.js CHANGED
@@ -18,7 +18,6 @@ const isIE = (req) => {
18
18
  * `data-app-data` on the script tag.
19
19
  * @param {Object} [settings={}] Settings object.
20
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
21
  * where the app should be mounted.
23
22
  * @param {Boolean} [settings.async=false] If the app script should be loaded asynchronously.
24
23
  * [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
@@ -30,35 +29,38 @@ const isIE = (req) => {
30
29
  */
31
30
  export function render(data, {
32
31
  html = '',
33
- selector = `[data-portlet-id="${appId}"]`,
34
32
  async = false,
35
33
  defer = true,
36
34
  req,
37
35
  } = {}) {
38
36
  const appMetadata = getAppMetadata();
39
37
 
38
+ const mountElement = `<div id="app_mount_${appId}">${html}</div>`;
39
+ const metaScriptTag = `<script id="app_meta_${appId}" type="application/json">${JSON.stringify(appMetadata)}</script>`;
40
+ const dataScriptTag = data ? `<script id="app_data_${appId}" type="application/json">${JSON.stringify(data)}</script>` : '';
41
+
40
42
  if (isOffline) {
41
43
  return `
42
- <div data-portlet-id="${appId}">${html}</div>
44
+ ${mountElement}
45
+ ${metaScriptTag}
46
+ ${dataScriptTag}
43
47
  <script>
44
48
  window.svDocReady(function() {
45
- var targetElement = document.querySelector('[data-portlet-id="${appId}"]');
49
+ var targetElement = document.getElementById('app_mount_${appId}');
46
50
  var script = document.createElement("script");
47
51
  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)}");
52
+ script.setAttribute("data-app-id", "${appId}");
51
53
  targetElement.parentNode.insertBefore(script, targetElement.nextSibling);
52
54
  });
53
55
  </script>`;
54
56
  }
55
57
  return `
56
- <div data-portlet-id="${appId}">${html}</div>
58
+ ${mountElement}
59
+ ${metaScriptTag}
60
+ ${dataScriptTag}
57
61
  <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
+ src="${getResourceUri('client/index.js')}?${appImportDate}${isIE(req) ? appId : ''}"
63
+ data-app-id="${appId}"
62
64
  ${async && !defer ? 'async' : ''}
63
65
  ${defer ? 'defer' : ''}>
64
66
  </script>`;