Can Svelte be used for templating like Handlebars? - server-side-rendering

My objective is to let end-users build some customization in my app. Can I do something like this? I know this is sometimes also referred to as liquid templates, similar to how handlebars.js works.
app.svelte
<script>
let name = 'world';
const template = '<h1> Hello {name} </h1>'
</script>
{#html template}
I'm sorry if this is already answered, but I could not find it.
Link to REPL.

This is a little bit hacky but it will do the trick:
<script>
let name = 'world';
let template;
</script>
<div class="d-none" bind:this={template}>
<h1>Hello {name}</h1>
</div>
{#html template?.innerHTML}
<style>
.d-none {
display: none;
}
</style>

If the template should be a string one solution might be to simply replace the {variable} with the values before displaying via a {#html} element >> REPL
Notice the warning in the SVELTE docs
Svelte does not sanitize expressions before injecting HTML. If the data comes from an untrusted source, you must sanitize it, or you are exposing your users to an XSS vulnerability.
(Inside a component $$props could be used to get the passed values in one object if they were handled seperately)
<script>
const props = {
greeting: 'Hello',
name: 'world'
}
let template = '<h1> {greeting} {name} </h1>'
let filledTemplate = Object.entries(props).reduce((template, [key,value]) => {
return template.replaceAll(`{${key}}`, value)
},template)
</script>
{#html filledTemplate}
Previous solution without string
To achieve this I would build a Component for every template and use a <svelte:component> element and a switch to display the selected one > REPL
[App.svelte]
<script>
import Template1 from './Template1.svelte'
import Template2 from './Template2.svelte'
let selectedTemplate = 'template1'
const stringToComponent = (str) => {
switch(str) {
case 'template1':
return Template1
case 'template2':
return Template2
}
}
</script>
<button on:click={() => selectedTemplate = 'template1'}>Template1</button>
<button on:click={() => selectedTemplate = 'template2'}>Template2</button>
<svelte:component this={stringToComponent(selectedTemplate)} adjective={'nice'}/>
[Template.svelte]
<script>
export let adjective
</script>
<hr>
<h1>This is a {adjective} template</h1>
<hr>

Well, you could do it, but that not what Svelte was designed for.
Svelte was designed to compile the template at build time.
I'd recommend using a template engine (like handlebars) for your use case.
A. Using Handlebars inside Svelte REPL:
<script>
import Handlebars from 'handlebars';
let name = 'world';
const template = "<h1> Hello {{name}} </h1>";
$: renderTemplate = Handlebars.compile(template);
</script>
{#html renderTemplate({ name })}
This of course limits the available syntax to handlebars, and you can't use svelte components inside a handlebar template.
B. Dynamic Svelte syntax templates inside a Svelte app
To be able to use svelte syntax you'll need to run the svelte compiler inside the frontend.
The output the compiler generates is not directly usable so you'll also need to run a bundler or transformer that is able to import the svelte runtime dependencies. Note that this is a separate runtime so using <svelte:component> wouldn't behave as expected, and you need to mount the component as a new svelte app.
In short, you could, but unless you're building a REPL tool you shouldn't.
C. Honourable mentions
Allow user to write markdown, this gives some flexibility (including using html) and use marked in the frontend to convert it to html.
Write the string replacements manually {#html template.replace(/\{name\}/, name)}

Related

What is the benefit to use useNuxtApp() on Nuxt3?

I'd like to know about new Nuxt3 feature called useNuxtApp.
Official document says, to use provide, you can do like below.
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
However it seems like you can also still use provide/inject.
For instance, I define the method 'hello' on parent component, then I also want to use it on child component, I can provide 'hello' for child from parent component and inject it.
You can still do same things by using provide/inject, so does anyone know what is the benefit using useNuxtApp?? And what is the difference between provide/inject and useNuxtApp except for syntax??
useNuxtApp built-in composable of Nuxt3
It is used to access shared runtime context of Nuxt available in server / client side, like: Vue App Instance, Runtime Hooks, Runtime Config Variables, Internal States etc.
Example:
i) ssrContext,
ii) payload,
iii) helper methods etc.
Values getting by this composable will be available across all composables, components, plugins (all files of .vue)
In Nuxt 2, this was referred to as "Nuxt Context"
Let's see an example how it can be use in Nuxt Plugin and by extending the plugin how it will give us a helper method and how we can use that helper method to get value to the rest of Nuxt Application.
say, we have a plugin in ~/plugins/my-plugin.js, where returning "provide" option as helper method.
in ~/plugins/my-plugin.js file
export default defineNuxtPlugin(() => {
...
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`
}
}
...
})
in any template (pages, components or any .vue file of the Nuxt App)
<template>
<div>
{{ $hello('world') }}
</div>
</template>
<script setup>
const { $hello } = useNuxtApp()
</script>
// or
<script setup>
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
</script setup>

How to override Handlebars' `if` helper

I'm looking for a way to change how Handlebars' if helper works.
Mandrill has made a few modifications to the standard Handlebars. One of these handy changes is in the way they handle Handlebars' if block helper. They've added the ability to evaluate expressions inside the if helper like this:
{{#if `purchases > 3`}}
I need to be able to compile Mandrill templates locally, for testing and preview, and this feature is making it difficult. I know I can write my own custom helpers, but in this case I'm looking for a way to alter the way the built-in if helper works.
I guess I could build my own version of Handlebars.
Any other suggestions?
I don't think that there is a problem to redefine the standard helpers as long as yours are working fine. You'll find in the below snippet one example that override the if helper to put instead a text. So just register your own helper this will override the default one. If you want the {{else}} also working you'll have to handle it in your if helper code.
$(document).ready(function () {
var context = {
"textexample1" : "hello",
};
Handlebars.registerHelper('if', function(text) {
return new Handlebars.SafeString(text);
});
var source = $("#sourceTemplate").html();
var template = Handlebars.compile(source);
var html = template(context);
$("#resultPlaceholder").html(html);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script id="sourceTemplate" type="text/x-handlebars-template">
{{#if 'test override'}}
{{textexample1}}
{{/if}}
</script>
<br/>
<div id="resultPlaceholder">
</div>

Pass dynamic data attribute to content block in Meteor

I have a dynamic template for buttons in Blaze, looks like this (simplified):
button.html
<template name="Button">
<button {{attributes}}>
<span class="button__text">{{> UI.contentBlock}}</span>
</button>
</template>
button.js
import {Template} from 'meteor/templating';
import cx from 'classnames';
import './button.html';
Template.Button.helpers({
attributes() {
const instance = Template.instance();
let {data} = instance;
return {
disabled: data.disabled,
'class': cx('button', data.class)
};
}
});
Attempt to set dynamic data attribute:
{{#Button class="js-add-contact" data-phase-index={{index}}}}Add Contact{{/Button}}
This insertion of index (let's assume it's just a simple, dynamic string) into data-phase-index throws an error: the content block was not expecting the {{. I am unsure of another way to get that dynamic data into the template. There's also the issue of getting the data- attributes recognized by Button in the attributes() helper. Can anyone clear this up?
Simply data-phase-index=index should work.
Since you are already within double curly braces for your Button template call, Meteor knows it will get interpreted values. For example, see that you have to use quotes around your string in class="js-add-contact".
As usual, Meteor will try to interpret index from a helper, or from data context.

Recursive component inclusion

I'm looking at the Authoring Ractive.js components document on github from Rich-Harris.
It starts with invoking the foo component and including it this way:
<link rel='ractive' href='foo.html' name='foo'>
<p>This is an imported 'foo' component: <foo/></p>
Which I understand as declaring foo.html as a component and calling it on the foo tag, and this would not require doing a ractive.load (although I did not understand yet where the data loading would occur).
As it does not work at all (no loading of the component), I'm wondering if I mis-understood this part.
Has anyone use this and could give me a complete example?
Components themselves are independent of the loading mechanism.
In the simplest form, components can be declared in javascript:
Ractive.components.foo = Ractive.extend({
template: '#foo' // id of script tag, or can be inline string,
// other options
});
var ractive = new Ractive({
template: '<p>This is an main view with a 'foo' component: <foo/></p>'
});
Creating components is covered here in the docs.
There are many ways to package and load components. Using the link tag, as in your example, requires using ractive-load to actually load the components:
<!-- name is optional if same as file name -->
<link rel='ractive' href='foo.html' name='foo'>
<script src='ractive.js'></script>
<script src='ractive-load.js'></script>
<script>
// calling load() with no parameters loads all link tags and
// registers them as global components
Ractive.load().then( function () {
var ractive = new Ractive({
el: 'body',
template: 'I have access to the <foo/> component!',
data: { ... }
});
// or you could instantiate a component via:
var app = new Ractive.components.app({
... /options
});
}).catch( handleError );
</script>

Meteor - TRIPLE template tag is not allowed in an HTML attribute error

I got error message when trying to run existing meteor project.
$meteor
=> Started proxy.
=> Started MongoDB.
=> Errors prevented startup:
While building the application:
client/coinmx.html:169: TRIPLE template tag is not allowed in an HTML attribute
...title="Totals: {{{get...
^
In Meteor 0.8, it's possible to return a Javascript object which is directly rendered into HTML attributes versus earlier versions, where you had to render it yourself.
Old version:
<input name={{name}} title={{title}}>
helpers:
Template.foo.name = "fooName";
Template.foo.title = "fooTitle";
New version:
<input {{attributes}}>
helpers:
Template.foo.attributes = {
name: "fooName",
title: "fooTitle"
};
All of these can be functions, and reactive, etc. Because the object is rendered directly into attributes, there is no need for you to SafeString some manually rendered content as before. This is the recommended way to go if need to render HTML attributes.
See also the following for how conditional attributes work under this scheme:
https://github.com/meteor/meteor/wiki/Using-Blaze#conditional-attributes-with-no-value-eg-checked-selected
The error is pretty much explanatory: you cannot use {{{something}}} inside a HTML attribute, you need to use {{something}} instead. Depending on what the something is (it's not known from your question as you didn't provide the code), that's either all you need to do, or you can achieve similar functionality by returning new Handlebars.SafeString("result") from your helper instead of just "result". However, if you do, you need to be super sure that the thing you'll return won't break the HTML structure.
Hugo's answer above gave me the missing piece I needed for the same issue-- triple stashes in 0.8 no longer supported. Here is an example that hopefully helps.
Where you might have had {{{resolve}}} in your template, you would now do:
<template name='thing'>
<ol>
{{#each all}}
{{resolve}}
{{/each}}
</ol>
<template>
The helper code then makes use of Spacebars.SafeString which looks to be preferred with Blaze:
Template.thing.helpers({
all: function () {
return Things.find();
},
resolve: function () {
var result = "<li>";
for (var i = 0; i < this.arrayOfClassNames.length; ++i)
result += <'div class='" + this.arrayOfClassNames[i] + "'></div>";
result += "</li>";
return new Spacebars.SafeString(result);
}
});
The key here is to return the 'new Spacebars.SafeString(result)' to wrap your HTML (which must be well formed).

Resources