Closed RafaelZasas closed 5 months ago
Essentially I need the each loop to be dynamic. Its impossible to know how many items are in the order before the order it placed. The built HTML needs to be able to render n
number of orderItems- where n
is the length of an array, which is provided by handlebars syntax.
Listing order items must be an extremely common practice. I just cant find where in the documentation it explains how to do it.
Help me understand, why the @
in @{{orderItems}}
?
{{ }}
expressions with a @
in front will be ignored in Maizzle so the loop would output nothing:
https://maizzle.com/docs/expressions#ignoring
You'd need to pass an actual array/object data to the loop
attribute there, is that not possible in your case?
@cossssmin I am ignoring the maizzle interpreted handlebars on purpose.
The handlebars need to be present in the final built output because the data is dynamic for each order. The html needs to be hydrated dynamically.
Is the solution to run npm run build
to build the outputs with the provided customer information and order data for each and every email sent?
@cossssmin I guess I may be misunderstanding the correct way to use maizzle. How am I supposed to be generating dynamic emails based on user interactions?
Currently, the firebase extension flow is as follows:
I add a document to the mail
collection in firestore, with this data structure:
export type MailData = {
to?: string | string[]
from?: string
replyTo?: string
message?: Message
template?: {
name: string
data?: { [key: string]: any }
}
toUids?: string[]
cc?: string[]
ccUids?: string[]
bcc?: string[]
bccUids?: string[]
headers?: any
}
type Message = {
subject?: string
text?: string
html?: string
messageId?: string
amp?: string
attachments?: Attachment[]
}
That then gets queued for sending. If a template is used instead of message, then the data object in the template is injected into handlebars.
What I am doing, is copying the build outputs from maizzle, and pasting them into the templates.
That is why I need the handlebars
Have a look at using Maizzle programmatically:
You'd pass your template.data
inside the maizzle
config that you provide to the render
method, i.e.:
const Maizzle = require('@maizzle/framework')
const options = {
maizzle: {
locals: {
order: template.data
}
},
}
Maizzle
.render(`html string`, options)
.then(({html}) => console.log(html))
With that setup you can loop over order
directly:
<each loop="item, index in order">...</each>
Mind the Tailwind config caveat, you need to pass it otherwise you'll get the default Tailwind config which doesn't play nice in emails. You can just read our tailwind.config.js
and provide it in the options
, something like:
const options = {
maizzle: {
locals: {
order: template.data
}
},
tailwind: {
config: JSON.parse(fs.readFileSync('path/to/tailwind.config.js'))
}
}
@cossssmin
Where would this code be?
const Maizzle = require('@maizzle/framework')
const options = {
maizzle: {
locals: {
order: template.data
}
},
}
Maizzle
.render(`html string`, options)
.then(({html}) => console.log(html))
I dont have a "backend" in this stack. Firebase abstracts that away. Ie. There is no NodeJS api that I control, other than the Frontend react I suppose.
It would seem slightly insane to bundle the entire maizzle templates directory with the front end and using the maizzle cli to build the html.
@cossssmin is Maizzle.render
the process being used to generate the dist outputs when npm run build
is executed?
If I were to completely bypass the build step, and instead use the file in the src/templates
directory, how would I have access to the components such as the header and footer? The html string
would reference components, how would the Maizzle.render function know where to find those?
Yeah to use it programmatically you need a Node backend, no other way around it.
If you're just doing npm run build
locally you need to provide the order
data in some way to Maizzle so it can loop over it. So if at first you're @-ignoring the {{orderItems}}
expression, the only option I see is that you re-run the build once you have the order data from Firebase and can provide it in the config.
You could probably use the afterBuild
event to rebuild everything again with the order data included in the config, but yeah it's doing double work and could even lead to unexpected results from the Transformers applied (I'd disable all of them if rebuilding).
This kind of scenario is exactly what the API/programmatic is for - get the data, render the template with it.
The render
method compiles your source HTML, i.e. it evaluates expressions and replaces component tags with their actual HTML. It doesn't output files, it just returns the compiled HTML string.
@cossssmin Would this work?:
Keep the @ignore symbols at build step so that maizzle stitches together components, but leaves handlebars in the html file.
Create nodeJS serverless function (for each email type, order confirmation, refund, delivery status etc.) that uses Maizzle.render to replace handlebars with provided variables. I assume that the {{vars}}
left over from the npm run build will still be picked up by maizzle.render.
It would but you're doing the work twice. If you have the variables like {{orderItems}}
available when running the serverless function you could just use Maizzle programmatically to compile a template with those variables as context.
Flow would be like:
orderItems
from your service, i.e. FirebaseMaizzle.render
in a serverless function
<x->
tags, uncompiled)options
as I explained above@cossssmin I see, however I'm still not sure how Maizzle.render
would stitch the components in the serverless function. I cant include the entire maizzle framework, with all the components, and all the templates in the serverless function. The directory (maizzle_templates) was cloned from a package I purchased which includes many different email templates, and is 172MB. That is far too large for the serverless function
Yes, for the components right now only file system is supported, meaning that it scans them on disk. So in the maizzle
config you'd provide, you'd need to also configure the paths where the components files live (and your serverless env must support file system, otherwise it can't work).
So you'd just need the component files, not the entire project. Btw it's 172MB probably because of node_modules, which you'd ignore anyway.
In the future we're looking into adding support for defining components right in the component config, so instead of scanning the file system it would use them from the config object - this will make it usable in more scenarios, including the browser. But it will take time, right now there's like one person working on this - me.
If your env doesn't support the file system, another idea would be to just inline the components. Especially if you're only using stuff like <x-spacer>
or <x-button>
, this wouldn't be complicated to do 👍
@cossssmin I appreciate your work. I would be happy to support you by purchasing your templates when it becomes available.
I love maizzle for the fact that the html is email client specific, and I can create emails with components. I would however like the option to inject variables into the full html string myself, using golang html/template or whatever html parser I choose. being confined to nodejs isnt ideal- I simply hate server side javascript and avoid it like the plague.
I love maizzle and have been using it for a few years now. This is the first time I needed to iterate over an array of objects in an email with maizzle templates.
Is it possible to have each loops that are not strictly defined at build time?
I am using the templates along with firebase trigger email extension which allows me to pass data to the template via handlebars.
I am trying to send order confirmation emails with a list of all the order items:
The
@{{orderItems}}
variable is provided to the extension (which uses nodemailer) inside of a object nameddata
.I am able to display everything else- including the payment info, the date, the name and any other attribute I include in the
data
object, and reference in the template via@{{ propertyName }}
.The issue is that when I build the templates, everything inside of the each tag simply disappears...
Is this because the maizzle framework cant evaluate the
@{{orderItems}}
array at build time and defaults to ignoring the loop entirely?Surely this is a common issue? How do other people send order confirmation emails or receipts with a list of items that the customer purchased.