@peter.naydenov/morph 3.2.0 → 3.3.1

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
@@ -2,6 +2,11 @@
2
2
 
3
3
 
4
4
 
5
+ ### 3.3.0 (2025-12-13)
6
+ - [x] Use other helper from inside of helper functionsHelper functions attribute - useHelper;
7
+
8
+
9
+
5
10
  ### 3.2.0 (2025-12-01)
6
11
  - [x] Conditional actions are depricated. ( starting with `?` ). Too unconsistant with other actions. It's can be solved with normal helper functions by providing a list of possible templates. It's something that is possible even now;
7
12
  - [x] Templates can have empty placeholders - just {{ }};
package/README.md CHANGED
@@ -10,18 +10,6 @@
10
10
 
11
11
 
12
12
 
13
- ## What's new in version 3.x.x
14
-
15
- In version 3 introduced `snippets`, you can choose to render only certain placeholders or groups of placeholders from the template. This lets you use templates as collections of reusable templates, so you can extract and use just the parts you need without having to render the whole template. This makes it easier to create and manage reusable template libraries for your project or rerender only the parts that need to be updated.
16
-
17
- The `render` function now takes a command as its first argument. Available commands are: `render`, `debug`, `snippets`, `curry`, and `set`. Other arguments have no changes. Just shifted right. The second argument becomes the data, the third is dependencies, and the fourth is a list of post-processing functions.
18
-
19
- In version 3.x.x, the data is always the second argument. It can be a string, as in version 2.x.x. The term "command" is no longer used for this argument; instead, it is called "instructions". Available instructions include: `raw`, `demo`, `handshake`, `placeholders`, and `count`.
20
-
21
- How to migrate to version 3.x.x, please read the [Migration guide](./Migration.guide.md). Read more about `snippets` down below.
22
-
23
-
24
-
25
13
  ## General Information
26
14
 
27
15
  `Morph` has a logic-less template syntax. Placeholders are places surrounded by double curly braces `{{ }}` and they represents the pleces where the data will be inserted.
@@ -259,6 +247,38 @@ Helpers are templates and functions that are used by actions to decorate the dat
259
247
  - `Extended render functions` will return a string like regular render functions, but will receive a deep branch of requested data;
260
248
  - `Conditional render functions` could return null, that means: ignore this action. The result could be also a string: the name of other helper function that will render the data.
261
249
 
250
+ ### Calling Helpers within Helpers
251
+
252
+ Starting from version 3.3.0, helper functions receive a `useHelper` function in their arguments object. This allows helpers to call other helpers programmatically.
253
+
254
+ ```js
255
+ const helpers = {
256
+ // Basic helper
257
+ format: ({ data }) => `[${data}]`,
258
+
259
+ // Helper using another helper
260
+ process: ({ data, useHelper }) => {
261
+ return useHelper('format') // Uses current data
262
+ },
263
+
264
+ // Helper overriding data
265
+ customProcess: ({ data, useHelper }) => {
266
+ return useHelper('format', 'Override') // Uses provided data
267
+ },
268
+
269
+ // Helper calling a template string helper
270
+ linkToCheck: ({ data, useHelper }) => {
271
+ // 'link' is a string template helper defined elsewhere
272
+ if (data.url) return useHelper('link', { text: data.name, href: data.url })
273
+ return data.name
274
+ }
275
+ }
276
+ ```
277
+
278
+ `useHelper` signature: `useHelper(helperName, [dataOverride])`
279
+ - `helperName`: String name of the helper to call.
280
+ - `dataOverride`: Optional. Data to pass to the helper. If omitted, the current data context of the caller is used.
281
+
262
282
 
263
283
 
264
284
  ## Commands
Binary file
@@ -0,0 +1,10 @@
1
+ export default actionData;
2
+ declare function actionData({ name }: {
3
+ name: any;
4
+ }, levelData: any, { helpers, extendArguments, nestedData, level, useHelper }: {
5
+ helpers: any;
6
+ extendArguments: any;
7
+ nestedData: any;
8
+ level: any;
9
+ useHelper: any;
10
+ }): void;
@@ -0,0 +1,10 @@
1
+ export default actionExtendedRender;
2
+ declare function actionExtendedRender({ name }: {
3
+ name: any;
4
+ }, levelData: any, { helpers, extendArguments, nestedData, level, useHelper }: {
5
+ helpers: any;
6
+ extendArguments: any;
7
+ nestedData: any;
8
+ level: any;
9
+ useHelper: any;
10
+ }): void;
@@ -0,0 +1,10 @@
1
+ export default actionMix;
2
+ declare function actionMix({ name }: {
3
+ name: any;
4
+ }, levelData: any, { helpers, extendArguments, nestedData, level, useHelper }: {
5
+ helpers: any;
6
+ extendArguments: any;
7
+ nestedData: any;
8
+ level: any;
9
+ useHelper: any;
10
+ }): void;
@@ -0,0 +1,13 @@
1
+ export default actionRender;
2
+ declare function actionRender({ name }: {
3
+ name: any;
4
+ }, levelData: any, { helpers, original, dependencies, useHelper, extendArguments, nestedData, level, iData }: {
5
+ helpers: any;
6
+ original: any;
7
+ dependencies: any;
8
+ useHelper: any;
9
+ extendArguments: any;
10
+ nestedData: any;
11
+ level: any;
12
+ iData: any;
13
+ }): void;
@@ -0,0 +1,8 @@
1
+ export function actionSave({ name }: {
2
+ name: any;
3
+ }, levelData: any, { memory }: {
4
+ memory: any;
5
+ }): void;
6
+ export function actionOverwrite({ name }: {
7
+ name: any;
8
+ }, levelData: any): any;
@@ -1,4 +1,12 @@
1
1
  export default build;
2
+ export type UseHelperFn = (name: string, data?: any) => any;
3
+ export type HelperFn = (args: {
4
+ data: any;
5
+ dependencies: object;
6
+ full?: any;
7
+ useHelper?: UseHelperFn;
8
+ memory?: object;
9
+ }) => any;
2
10
  export type Template = {
3
11
  /**
4
12
  * - Data to be rendered;
@@ -7,17 +15,35 @@ export type Template = {
7
15
  /**
8
16
  * - Optional. Object with helper functions or simple templates for this template;
9
17
  */
10
- helpers?: object;
18
+ helpers?: {
19
+ [x: string]: string | HelperFn;
20
+ };
11
21
  /**
12
22
  * - Optional. Example for data to be rendered with;
13
23
  */
14
24
  handshake?: object;
15
25
  };
16
26
  export type tupleResult = any[];
27
+ /**
28
+ * @callback UseHelperFn
29
+ * @param {string} name - Name of the helper to call.
30
+ * @param {any} [data] - Optional data override.
31
+ * @returns {any} Result of the helper call.
32
+ */
33
+ /**
34
+ * @callback HelperFn
35
+ * @param {object} args
36
+ * @param {any} args.data - The data context.
37
+ * @param {object} args.dependencies - Injected dependencies.
38
+ * @param {any} [args.full] - Full data context.
39
+ * @param {UseHelperFn} [args.useHelper] - Function to call other helpers.
40
+ * @param {object} [args.memory] - internal memory state.
41
+ * @returns {any} Rendered output.
42
+ */
17
43
  /**
18
44
  * @typedef {Object} Template
19
45
  * @property {string} template - Data to be rendered;
20
- * @property {object} [helpers] - Optional. Object with helper functions or simple templates for this template;
46
+ * @property {Object.<string, HelperFn|string>} [helpers] - Optional. Object with helper functions or simple templates for this template;
21
47
  * @property {object} [handshake] - Optional. Example for data to be rendered with;
22
48
  */
23
49
  /**
@@ -0,0 +1,11 @@
1
+ export default executeActions;
2
+ declare function executeActions({ nestedData, actSetup, helpers, original, dependencies, memory, dElement, createUseHelper }: {
3
+ nestedData: any;
4
+ actSetup: any;
5
+ helpers: any;
6
+ original: any;
7
+ dependencies: any;
8
+ memory: any;
9
+ dElement: any;
10
+ createUseHelper: any;
11
+ }): any;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Handles debug commands for template inspection.
3
+ *
4
+ * @param {string} d - Debug instruction
5
+ * @param {object} context - Context object
6
+ * @param {object} context.handshake - Example data
7
+ * @param {object} context.helpers - Helper functions
8
+ * @param {array} context.placeholders - Placeholder definitions
9
+ * @param {array} context.cuts - Chopped template parts
10
+ * @returns {any} Debug result or error message
11
+ */
12
+ export function handleDebug(d: string, { handshake, helpers, placeholders, cuts }: {
13
+ handshake: object;
14
+ helpers: object;
15
+ placeholders: any[];
16
+ cuts: any[];
17
+ }): any;
18
+ /**
19
+ * Handles the 'set' command to modify template properties.
20
+ *
21
+ * @param {object} d - Modification data
22
+ * @param {object} context - Context object
23
+ * @param {object} context.helpers - Current helper functions
24
+ * @param {object} context.handshake - Current handshake data
25
+ * @param {array} context.placeholders - Current placeholders
26
+ * @param {array} context.chop - Current chopped template
27
+ * @param {function} context.build - Build function
28
+ * @param {object} context.buildDependencies - Build dependencies
29
+ * @returns {function} Modified template function
30
+ */
31
+ export function handleSet(d: object, { helpers, handshake, placeholders, chop, build, buildDependencies }: {
32
+ helpers: object;
33
+ handshake: object;
34
+ placeholders: any[];
35
+ chop: any[];
36
+ build: Function;
37
+ buildDependencies: object;
38
+ }): Function;
39
+ /**
40
+ * Handles snippets command to select specific placeholders.
41
+ *
42
+ * @param {string} command - Snippets command
43
+ * @param {object} snippets - Snippets mapping
44
+ * @returns {array|null} Selected placeholders or null
45
+ */
46
+ export function handleSnippets(command: string, snippets: object): any[] | null;
@@ -0,0 +1,27 @@
1
+ export default processPlaceholders;
2
+ /**
3
+ * Processes placeholders in the template with provided data and context.
4
+ *
5
+ * @param {object} params - Parameters object
6
+ * @param {array} params.d - Data array to process
7
+ * @param {array} params.chop - Chopped template parts
8
+ * @param {array} params.placeholders - Placeholder definitions
9
+ * @param {any} params.original - Original data context
10
+ * @param {object} params.helpers - Helper functions
11
+ * @param {object} params.dependencies - Injected dependencies
12
+ * @param {object} params.memory - Internal memory state
13
+ * @param {array} params.args - Additional arguments
14
+ * @param {boolean} params.onlySnippets - Whether to process only snippets
15
+ * @returns {array} Processed template parts
16
+ */
17
+ declare function processPlaceholders({ d, chop, placeholders, original, helpers, dependencies, memory, args, onlySnippets }: {
18
+ d: any[];
19
+ chop: any[];
20
+ placeholders: any[];
21
+ original: any;
22
+ helpers: object;
23
+ dependencies: object;
24
+ memory: object;
25
+ args: any[];
26
+ onlySnippets: boolean;
27
+ }): any[];
@@ -1,25 +1,14 @@
1
1
  export default render;
2
2
  /**
3
- * Executes rendering and returns the rendered result.
3
+ * Renders a helper or template with the provided data and context.
4
4
  *
5
- * Handles both function-based and template-based rendering. Normalizes data objects
6
- * by converting nested objects to their 'text' properties and arrays to their first elements.
5
+ * @param {any} theData - The data to process.
6
+ * @param {string} name - The name of the helper or template to render.
7
+ * @param {object} helpers - Dictionary of available helpers.
8
+ * @param {any} original - The full original data context.
9
+ * @param {object} dependencies - injected dependencies.
10
+ * @param {...any} args - Additional arguments.
7
11
  *
8
- * @param {object|string} theData - Data to be rendered. If string, becomes the 'text' property value.
9
- * @param {string} name - Name of the render helper/template to execute
10
- * @param {object} helpers - Object containing helper functions and templates
11
- * @param {object} original - Original data context for full data access
12
- * @param {object} dependencies - External dependencies available to helpers
13
- * @param {...any} args - Additional arguments passed to the render function
14
- *
15
- * @returns {string} Rendered string result
16
- *
17
- * @example
18
- * // Render with function helper
19
- * const result = render(data, 'myHelper', helpers, originalData, deps);
20
- *
21
- * @example
22
- * // Render with template
23
- * const result = render(data, 'myTemplate', helpers, originalData, deps);
12
+ * @returns {any} The result of the rendering process, or an error string if the helper is not available.
24
13
  */
25
- declare function render(theData: object | string, name: string, helpers: object, original: object, dependencies: object, ...args: any[]): string;
14
+ declare function render(theData: any, name: string, helpers: object, original: any, dependencies: object, ...args: any[]): any;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peter.naydenov/morph",
3
3
  "description": "Template engine",
4
- "version": "3.2.0",
4
+ "version": "3.3.1",
5
5
  "license": "MIT",
6
6
  "author": "Peter Naydenov",
7
7
  "main": "./src/main.js",
@@ -26,17 +26,23 @@ function _renderHolder ( template, data ) {
26
26
  chop = _chopTemplate (settings)( template )
27
27
  , set = settings
28
28
  ;
29
- chop.forEach ( (item,i) => {
30
- const isPlaceholder = item.includes ( set.TG_PRX )
31
- if ( isPlaceholder ) {
32
- const field = item.replace ( set.TG_PRX, '' ).replace ( set.TG_SFX, '' ).trim();
33
- if ( data.hasOwnProperty (field) && data[field] != null ) chop[i] = data[field]
34
- } // if isPlaceholder
35
- }) // forEach chop
29
+ if ( typeof chop === 'string' ) return chop
30
+ chop.forEach ( ( item, i ) => {
31
+ const isPlaceholder = item.includes ( set.TG_PRX )
32
+ if ( isPlaceholder ) {
33
+ const field = item.replace(set.TG_PRX, '').replace(set.TG_SFX, '').trim();
34
+ if (data.hasOwnProperty(field) && data[field] != null) {
35
+ let val = data [ field ]
36
+ if ( typeof val === 'object' && val.text ) val = val.text
37
+ chop[i] = val
38
+ }
39
+ } // if isPlaceholder
40
+ }) // forEach chop
36
41
  return chop.join ( '' )
37
42
  } // _renderHolder func.
38
43
 
39
44
 
45
+
40
46
  export default _renderHolder
41
47
 
42
48
 
@@ -0,0 +1,30 @@
1
+ import _defineDataType from "./_defineType.js"
2
+
3
+ function actionData({ name }, levelData, { helpers, extendArguments, nestedData, level, useHelper }) {
4
+ const theData = levelData
5
+ let dataType = _defineDataType(theData)
6
+
7
+ switch (dataType) {
8
+ case 'array':
9
+ theData.forEach((d, i) => {
10
+ const localUseHelper = useHelper(d);
11
+ theData[i] = (d instanceof Function) ? helpers[name]({ data: d(), ...extendArguments, full: d, useHelper: localUseHelper }) : helpers[name]({ data: d, ...extendArguments, full: d, useHelper: localUseHelper })
12
+ })
13
+ break
14
+ case 'object':
15
+ const uhObj = useHelper(theData);
16
+ nestedData[level] = [helpers[name]({ data: theData, ...extendArguments, full: theData, useHelper: uhObj })]
17
+ break
18
+ case 'function':
19
+ const fnData = theData();
20
+ const uhFn = useHelper(fnData);
21
+ nestedData[level] = [helpers[name]({ data: fnData, ...extendArguments, full: theData, useHelper: uhFn })]
22
+ break
23
+ case 'primitive':
24
+ const uhPrim = useHelper(theData);
25
+ nestedData[level] = helpers[name]({ data: theData, ...extendArguments, full: theData, useHelper: uhPrim })
26
+ break
27
+ } // switch dataType
28
+ }
29
+
30
+ export default actionData
@@ -0,0 +1,28 @@
1
+ import _defineDataType from "./_defineType.js"
2
+
3
+ function actionExtendedRender({ name }, levelData, { helpers, extendArguments, nestedData, level, useHelper }) {
4
+ // levelData is unused?
5
+ // build.js used `nestedData[0]`.
6
+ // "nestedData[0].forEach((d, i) => {"
7
+ // "const uh = createUseHelper(d);"
8
+ // "nestedData[0][i] = helpers[name]({ data: d, ...extendArguments, full: d, useHelper: uh })"
9
+
10
+ // This seems to assume extendedRender operates on ROOT data (nestedData[0])?
11
+ // Yes, `nestedData[0]` is typically the initial data list.
12
+
13
+ const isValid = typeof helpers[name] === 'function';
14
+
15
+ if (isValid) {
16
+ if (nestedData[0] && Array.isArray(nestedData[0])) {
17
+ nestedData[0].forEach((d, i) => {
18
+ const uh = useHelper(d);
19
+ nestedData[0][i] = helpers[name]({ data: d, ...extendArguments, full: d, useHelper: uh })
20
+ })
21
+ }
22
+ }
23
+ else {
24
+ // TODO: Error...
25
+ }
26
+ }
27
+
28
+ export default actionExtendedRender
@@ -0,0 +1,66 @@
1
+ import _defineDataType from "./_defineType.js"
2
+ import walk from '@peter.naydenov/walk'
3
+
4
+ function actionMix({ name }, levelData, { helpers, extendArguments, nestedData, level, useHelper }) {
5
+ const theData = levelData
6
+ let dataType = _defineDataType(theData)
7
+
8
+ if (name === '') { // when is anonymous mixing action
9
+ switch (dataType) {
10
+ case 'object':
11
+ let kTest = Object.keys(theData).find(k => k.includes('/')); // Check if keys are breadcrumbs
12
+ if (kTest) Object.entries(theData).forEach(([k, v]) => nestedData[level][k] = v['text'])
13
+ else nestedData[level] = theData['text']
14
+ for (let i = level - 1; i >= 0; i--) {
15
+ nestedData[i] = walk({ data: nestedData[i], objectCallback: check })
16
+ }
17
+ function check({ value, breadcrumbs }) {
18
+ if (nestedData[level][breadcrumbs]) return nestedData[level][breadcrumbs]
19
+ return value
20
+ } // check func.
21
+ break
22
+ case 'array':
23
+ theData.forEach((x, i) => {
24
+ if (i > 0) {
25
+ let xType = _defineDataType(x);
26
+ if (x == null) return
27
+ if (xType === 'object') theData[0] += `${x.text}`
28
+ else theData[0] += `${x}`
29
+ theData.toSpliced(i, 1)
30
+ }
31
+ else {
32
+ let xxType = _defineDataType(x);
33
+ theData[0] = ''
34
+ if (xxType === null) return
35
+ else if (xxType === 'object') theData[0] = `${x.text}`
36
+ else theData[0] = `${x}`
37
+ }
38
+ }) // forEach theData
39
+ theData.length = 1
40
+ break
41
+ } // switch dataType
42
+ } // if name === ''
43
+ else {
44
+ const dType = _defineDataType ( theData ); // 'd' in build.js was 'd' from loop, here 'theData'
45
+ const localUseHelper = useHelper ( theData );
46
+ let val = helpers[name]({ data: theData, ...extendArguments, full: theData, useHelper: localUseHelper }); // 'full: d' in build.js, here 'theData'
47
+ let valType = _defineDataType(val);
48
+
49
+ theData.forEach((x, i) => theData.splice(i, 1))
50
+ theData.length = 0
51
+ switch (valType) {
52
+ case 'primitive':
53
+ theData[0] = val
54
+ break
55
+ case 'array':
56
+ theData.push(...val)
57
+ break
58
+ } // switch valType
59
+ }
60
+ } // actionMix func.
61
+
62
+
63
+
64
+ export default actionMix
65
+
66
+
@@ -0,0 +1,86 @@
1
+ import _defineDataType from "./_defineType.js"
2
+ import render from "./render.js"
3
+
4
+ function actionRender({ name }, levelData, { helpers, original, dependencies, useHelper, extendArguments, nestedData, level, iData }) {
5
+ /**
6
+ * logic from build.js 'render' case
7
+ * levelData is the array we are iterating (nestedData[level] or similar)
8
+ * But wait, build.js iterates levelData.
9
+ * "levelData.forEach((theData, iData) => {"
10
+ * So actionRender receives a SINGLE item?
11
+ * No, existing logic handles 'array' dataType inside the switch.
12
+ * But the loop inside build.js ALREADY iterates levelData.
13
+ * Line 276: `levelData.forEach((theData, iData) => { ... switch(type) ... })`
14
+ * So 'theData' is a single item from the level.
15
+ * BUT 'dataType' switch inside handles 'array'?
16
+ * _defineDataType(theData).
17
+ * If 'theData' is array, it iterates it.
18
+ * Yes.
19
+ */
20
+
21
+ const theData = levelData
22
+ let dataType = _defineDataType(theData)
23
+ const isRenderFunction = typeof helpers[name] === 'function';
24
+
25
+ switch (dataType) {
26
+ case 'array':
27
+ if (isRenderFunction) {
28
+ theData.forEach((d, i) => {
29
+ if (d == null) return
30
+ const dType = _defineDataType(d);
31
+ // useHelper is specialized for 'd' ??
32
+ // in build.js: const useHelper = createUseHelper(d);
33
+ // access to 'createUseHelper' needed?
34
+ // Yes.
35
+ // So we pass 'createUseHelper' factory, not the instance?
36
+ // Or we pass the instance if it was already created?
37
+ // In build.js loop: "const useHelper = createUseHelper(d);" happens INSIDE case 'route'/'data'/'render' loops usually.
38
+ // But here we are inside case 'render'.
39
+ // So we need 'createUseHelper' passed in context.
40
+
41
+ const localUseHelper = useHelper(d)
42
+ const text = helpers[name]({ data: d, ...extendArguments, full: original, useHelper: localUseHelper });
43
+
44
+ if (text == null) theData[i] = null
45
+ if (dType === 'object') d['text'] = text
46
+ else theData[i] = text
47
+ })
48
+ }
49
+ else {
50
+ theData.forEach((d, i) => {
51
+ if (d == null) return
52
+ const
53
+ dType = _defineDataType(d)
54
+ , text = render(d, name, helpers, original, dependencies)
55
+ ;
56
+ if (text == null) theData[i] = null
57
+ else if (dType === 'object') d['text'] = text
58
+ else theData[i] = text
59
+ })
60
+ }
61
+ break
62
+ case 'function':
63
+ const fD = theData();
64
+ const useHelperFn = useHelper(fD);
65
+ nestedData[level] = helpers[name]({ data: fD, ...extendArguments, full: theData, useHelper: useHelperFn })
66
+ break
67
+ case 'primitive':
68
+ if (isRenderFunction) {
69
+ const uhPrim = useHelper(theData);
70
+ nestedData[level] = helpers[name]({ data: theData, ...extendArguments, full: theData, useHelper: uhPrim })
71
+ }
72
+ else nestedData[level] = render(theData, name, helpers, original, dependencies)
73
+ break
74
+ case 'object':
75
+ if (isRenderFunction) {
76
+ const uhO = useHelper(theData);
77
+ nestedData[level][iData]['text'] = helpers[name]({ data: theData, ...extendArguments, full: theData, useHelper: uhO })
78
+ }
79
+ else {
80
+ theData['text'] = render(theData, name, helpers, original, dependencies)
81
+ }
82
+ break
83
+ } // switch renderDataType
84
+ }
85
+
86
+ export default actionRender
@@ -0,0 +1,9 @@
1
+ function actionSave({ name }, levelData, { memory }) {
2
+ memory[name] = structuredClone(levelData)
3
+ }
4
+
5
+ function actionOverwrite({ name }, levelData) {
6
+ return structuredClone(levelData)
7
+ }
8
+
9
+ export { actionSave, actionOverwrite }