Skip to content

Commit

Permalink
feat: add at-variables (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmjuanes authored Apr 27, 2024
1 parent 57956e5 commit d0cc870
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 14 deletions.
74 changes: 68 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ const result = m("{{^isAdmin}}You are not Admin{{/isAdmin}}", data);
// Output: 'You are not Admin'
```

### Partials (added in v0.3.0)
### Partials

> This feature was added in `v0.3.0`
Partials allow you to include separate templates within your main template. Use the greater than symbol `>` followed by the partial name inside double curly braces `{{> partialName }}`.

Expand All @@ -87,7 +89,9 @@ const result = m("{{> hello}}", data, {partials});
// Output: 'Hello Bob!'
```

#### Custom context in partials (added in v0.3.1)
#### Custom context in partials

> This feature was added in `v0.3.1`.
You can provide a custom context for the partial by specifying a field of the data: `{{> partialName dataField}}`.

Expand All @@ -106,7 +110,9 @@ const result = m("User: {{> user currentUser}}", data, {partials});
// Output: 'User: John Doe <[email protected]>'
```

### Built-in helpers (added in v0.4.0)
### Built-in helpers

> Added in `v0.4.0`.
Helpers allows you to execute special functions within blocks or sections of your template. Mikel currently supports the following built-in helpers:

Expand All @@ -126,6 +132,16 @@ const data = {
console.log(m("{{#each users}}{{.}}, {{/each}}", data)); // --> 'John, Alice, Bob, '
```

When looping throug arrays, you can use the variable `@index` to access to the current index of the item in the array:

```javascript
const data = {
users: ["John", "Alice", "Bob"],
};

console.log(m("{{#each users}}{{@index}}: {{.}}, {{/each}}", data)); // --> '0: John, 1: Alice, 2: Bob, '
```

The `each` helper can also iterate over objects:

```javascript
Expand All @@ -135,7 +151,20 @@ const data = {
},
};

console.log(m("{{#each values}}{{@key}}: {{.}}{{/each}}", data)); // --> 'foo: bar'
console.log(m("{{#each values}}{{.}}{{/each}}", data)); // --> 'bar'
```

When looping throug objects, you can use the variable `@key` to access to the current key in the object, and the variable `@value` to access to the corresponding value:

```javascript
const data = {
values: {
foo: "0",
bar: "1",
},
};

console.log(m("{{#each values}}{{@key}}: {{@value}}, {{/each}}", data)); // --> 'foo: 0, bar: 1, '
```

#### if
Expand All @@ -148,7 +177,7 @@ Example:

```javascript
const data = {
isAdmin: true;
isAdmin: true,
};

console.log(m("{{#if isAdmin}}Hello admin{{/if}}", data)); // --> 'Hello admin'
Expand All @@ -164,12 +193,45 @@ Example:

```javascript
const data = {
isAdmin: false
isAdmin: false,
};

console.log(m("{{#unless isAdmin}}Hello guest{{/unless}}", data)); // --> 'Hello guest'
```

### At-Variables

> Added in `v0.4.0`.
At-Variables in Mikel provide convenient access to special values within your templates. These variables, denoted by the `@` symbol, allow users to interact with specific data contexts or values.

#### @root

The `@root` variable grants access to the root data context provided to the template. It is always defined and enables users to retrieve values from the top-level data object.

Example:

```javascript
const data = {
name: "World",
};

console.log(m("Hello, {{@root.name}}!", data)); // -> 'Hello, World!'
```

#### @index

The `@index` variable facilitates access to the current index of the item when iterating over an array using the `#each` helper. It aids in dynamic rendering and indexing within loops.

#### @key

The `@key` variable allows users to retrieve the current key of the object entry when looping through an object using the `#each` helper. It provides access to object keys for dynamic rendering and customization.

#### @value

The `@value` variable allows users to retrieve the current value of the object entry when iterating over an object using the `#each` helper. It simplifies access to object values for dynamic rendering and data manipulation.


## API

### `m(template, data[, options])`
Expand Down
16 changes: 8 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const escape = str => {
};

const get = (ctx, path) => {
return (path === "." ? ctx : path.split(".").reduce((p, k) => p?.[k], ctx)) || "";
return (path === "." ? ctx : path.split(".").reduce((p, k) => p?.[k], ctx)) ?? "";
};

const helpers = new Map(Object.entries({
Expand All @@ -32,7 +32,7 @@ const compile = (tokens, output, context, opt, index = 0, section = "", vars = {
output.push(tokens[i]);
}
else if (tokens[i].startsWith("@")) {
output.push(vars?.[tokens[i].slice(1).trim() || "_"] ?? "");
output.push(get(vars || {}, tokens[i].slice(1).trim() ?? "_") ?? "");
}
else if (tokens[i].startsWith("!")) {
output.push(get(context, tokens[i].slice(1).trim()));
Expand All @@ -44,13 +44,13 @@ const compile = (tokens, output, context, opt, index = 0, section = "", vars = {
context: context,
globalOptions: opt,
fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
i = compile(tokens, blockOutput, blockContext, opt, j, t, blockVars);
i = compile(tokens, blockOutput, blockContext, opt, j, t, {root: vars.root, ...blockVars});
return blockOutput.join("");
},
}));
// Make sure that this block has been executed
if (i + 1 === j) {
i = compile(tokens, [], {}, opt, j, t);
i = compile(tokens, [], {}, opt, j, t, vars);
}
}
else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
Expand All @@ -60,18 +60,18 @@ const compile = (tokens, output, context, opt, index = 0, section = "", vars = {
if (!negate && value && Array.isArray(value)) {
const j = i + 1;
(value.length > 0 ? value : [""]).forEach(item => {
i = compile(tokens, value.length > 0 ? output : [], item, opt, j, t);
i = compile(tokens, value.length > 0 ? output : [], item, opt, j, t, vars);
});
}
else {
const includeOutput = (!negate && !!value) || (negate && !!!value);
i = compile(tokens, includeOutput ? output : [], context, opt, i + 1, t);
i = compile(tokens, includeOutput ? output : [], context, opt, i + 1, t, vars);
}
}
else if (tokens[i].startsWith(">")) {
const [t, v] = tokens[i].slice(1).trim().split(" ");
if (typeof opt?.partials?.[t] === "string") {
compile(opt.partials[t].split(tags), output, v ? get(context, v) : context, opt, 0, "");
compile(opt.partials[t].split(tags), output, v ? get(context, v) : context, opt, 0, "", vars);
}
}
else if (tokens[i].startsWith("/")) {
Expand All @@ -89,6 +89,6 @@ const compile = (tokens, output, context, opt, index = 0, section = "", vars = {
};

export default (str, context = {}, opt = {}, output = []) => {
compile(str.split(tags), output, context, opt, 0, "");
compile(str.split(tags), output, context, opt, 0, "", {root: context});
return output.join("");
};
24 changes: 24 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,27 @@ describe("[helpers] {{#unless }}", () => {
assert.equal(m("_{{#unless value}}Yes!{{/unless}}_", {value: false}), "_Yes!_");
});
});

describe("[at-variable] {{@root}}", () => {
it("should reference the global context", () => {
assert.equal(m("{{#each values}}{{@root.key}}{{/each}}", {values: ["a", "b"], key: "c"}), "cc");
});
});

describe("[at-variable] {{@index}}", () => {
it("sould reference current index in the array", () => {
assert.equal(m("{{#each values}}{{@index}}{{/each}}", {values: ["a", "b", "c"]}), "012");
});
});

describe("[at-variable] {{@key}}", () => {
it("sshould reference current key when looping throug an object", () => {
assert.equal(m("{{#each values}}{{@key}},{{/each}}", {values: {foo: 1, bar: 2}}), "foo,bar,");
});
});

describe("[at-variable] {{@value}}", () => {
it("sshould reference current value when looping throug an object", () => {
assert.equal(m("{{#each values}}{{@value}},{{/each}}", {values: {foo: 1, bar: 2}}), "1,2,");
});
});

0 comments on commit d0cc870

Please sign in to comment.