Handlebars is saying it's missing the helper? - express

I'm trying to render a handlebars template manually server side from express.
//get handlebars
var hbs = require('hbs').create({
svg: require('handlebars-helper-svg'),
switch: require('../helpers/switch.js'),
case: require('../helpers/case.js'),
});
console.log(hbs)
//convert each entry to HTML via handlebars
var resultsHtml = results.map(function (tutorial) {
var template = require('../views/list-item.hbs');
template = hbs.compile(template);
return template(tutorial);
});
The log of hbs gives this:
Instance {
handlebars:
{ svg: { [Function] cache: {} },
switch: [Function],
case: [Function] },
cache: {},
__express: [Function: bound middleware],
SafeString: undefined,
Utils: undefined }
Which seems like it has the 3 helpers, but it keeps spitting out the error
Error: Missing helper: "svg"
Unless I remove all the SVGs from the template.
I've used this helper from my main app in app.js like this:
var svg = require('handlebars-helper-svg');
hbs.registerHelper('svg', svg);
But that doesn't work within my route, either way.
What am I doing wrong?

Problem was not with the helper, but rather with the template. Require is for javascript files and must have modules.export called in them. For other files you need to use the filesystem object to read the file:
fs.readFileSync('./views/list-item.hbs', 'utf8')

Related

How to use vue component across multiple node projects?

I'm trying to build a website builder within the drag-and-drop abilities via using Vue3. So, the user will be playing with the canvas and generate a config structure that going to post the backend. Furthermore, the server-side will generate static HTML according to this config.
Eventually, the config will be like the below and it works perfectly. The config only can have HTML tags and attributes currently. Backend uses h() function to generate dom tree.
My question is: can I use .vue component that will generate on the server side as well? For example, the client-side has a Container.vue file that includes some interactions, styles, etc. How can the backend recognize/resolve this vue file?
UPDATE:
Basically, I want to use the Vue component that exists on the Client side on the backend side to generate HTML strings same as exactly client side. (including styles, interactions etc).
Currently, I'm able to generate HTML string via the below config but want to extend/support Vue component itself.
Note: client and server are completely different projects. Currently, server takes config and runs createSSRApp, renderToString methods.
Here is the gist of how server would handle the API:
https://gist.github.com/yulafezmesi/162eafcf7f0dcb3cb83fb822568a6126
{
id: "1",
tagName: "main",
root: true,
type: "container",
properties: {
class: "h-full",
style: {
width: "800px",
transform: "translateZ(0)",
},
},
children: [
{
id: "9",
type: "image",
tagName: "figure",
interactive: true,
properties: {
class: "absolute w-28",
style: {
translate: "63px 132px",
},
},
},
],
}
This might get you started: https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
From the docs:
// this runs in Node.js on the server.
import { createSSRApp } from 'vue'
// Vue's server-rendering API is exposed under `vue/server-renderer`.
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button #click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
console.log(html)
})
I guess extract the template from request or by reading the submitted Vue file and use that as the template parameter value

How to create a file download button? <a href> and Axios not working

I'm trying to create a download button on my personal website for people to download my docx resume, but had some issues.
first i did it with simple href link thingy like
<a href="xxx.docx" download><button>download my resume</button></a>
but didn't work.
then i tried axois way, creating a button with the click action bind to the downloadFile(){} method, didn't work, coming with the error
GET http://localhost:8080/assets/assets/imgs/cv_eudora.docx 404 (Not Found)
Uncaught (in promise) Error: Request failed with status code 404
at createError (createError.js?2d83:16)
at settle (settle.js?467f:17)
at XMLHttpRequest.handleLoad (xhr.js?b50d:59)
I think it's because the url part in the downloadFile(){} function that's not stated properly, but don't know the right way to write the path in vue. The path itself should be right because it even had the automatic hint options all the way when i did it.
<button #click="downloadFile()">download my resume</button>
downloadFile() {
axios({
url: "../assets/imgs/cv_eudora.docx",
method: "GET",
responseType: "blob" // important
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "eudoraCV.docx"); //or any other extension
document.body.appendChild(link);
link.click();
});
}
The issue here is that the Webpack loaders do not apply to <a href> URLs so they won't be included in your build by default.
You have two options here...
Put your file in the public folder and reference it like this
export default {
// add the base URL to your component's "data" function
data: () => ({ publicPath: process.env.BASE_URL })
}
<a :href="`${publicPath}cv_eudora.docx`" download>
download my resume
</a>
or
Explicitly import your file using the require() function
<a :href="require('../assets/imgs/cv_eudora.docx')" download="cv_eudora.docx">
download my resume
</a>
For this to work however, you need to configure Webpack to load .docx files via the file-loader. In vue.config.js, you can tell Webpack to bundle documents by adding a new module rule...
module.exports = {
chainWebpack: config => {
config.module.rule('downloads')
// bundle common document files
.test(/\.(pdf|docx?|xlsx?|csv|pptx?)(\?.*)?$/)
.use('file-loader')
// use the file-loader
.loader('file-loader')
// bundle into the "downloads" directory
.options({ name: 'downloads/[name].[hash:8].[ext]' })
}
}
See https://cli.vuejs.org/guide/webpack.html#adding-a-new-loader

How to change content of VuePress page via Plugin

I am trying to set up a plugin to change the content of a VuePress markdown file on the dev server and production build. According to documentation, I should be able to use the _content and _strippedContent that is available to me with the extendPageData
The following code is what I have set up in a plugin to do this.
module.exports = (options = {}, context) => ({
extendPageData($page) {
const {
_filePath, // file's absolute path
_computed, // access the client global computed mixins at build time, e.g _computed.$localePath.
_content, // file's raw content string
_strippedContent, // file's content string without frontmatter
key, // page's unique hash key
frontmatter, // page's frontmatter object
regularPath, // current page's default link (follow the file hierarchy)
path, // current page's real link (use regularPath when permalink does not exist)
} = $page
$page._content = "replaced"
$page._strippedContent = "replaced"
}
})
The best I can tell is that this code should work as it updates the $page._content however it is not showing testing but rather the original content.
I know that I am getting into this code as I can console.log from the file and it shows in the console.
I fear that $page._content is immutable and wonder if there is a way to do this kind of content swapping during dev or build
The information in those page objects is used after the markdown has been compiled and during the Vue component rendering. The content is more there for reference and modifying it won't have the effect you are after.
This tripped me up as well.
All the markdown files are processed for inoformation, but then the actual compilation occurs through webpack. The general flow is:
.md -> markdown-loader -> vue-loader -> ...
My recommendation and what I have done is to create a custom webpack loader to modify the content before it goes through the VuePress markdown loader. I used this approach to run my markdown files through Nunjucks for templating pre-markdown compilation. It's increadibly easy to do this after you figure out the correct approach :) Here is a working approach:
config.js:
chainWebpack: config => {
config.module
.rule('md')
.test(/\.md$/)
.use(path.resolve(__dirname, './nunjucks'))
.loader(path.resolve(__dirname, './nunjucks'))
.end()
},
And then a simple loader can look like this(abridged):
module.exports = function(source) {
const rendered = nunjucks.renderString(source, config)
return rendered
}
I think that You should do this with use of extendMarkdown https://v1.vuepress.vuejs.org/config/#markdown-extendmarkdown.
Try like this
// index.js
module.exports = () => ({
name: 'vuepress-plugin-foo-bar',
extendMarkdown: md => {
const render = md.render;
md.render = (...args) => {
// original content
const html = render.call(md, ...args);
return 'new content';
};
},
});
You can to modify your .vuepress/config.js. For example, if you want to replace '---my text---' with 'MY TEXT' (with uppercase, for example) in all yours files markdown, you have to add the next code to .vuepress/config.js, into chainWebpack section where I use a regex expression:
// File: .vuepress/config.js
module.exports = {
...,
chainWebpack: config => {
// Each loader in the chain applies transformations to the processed resource:
config.module
.rule('md')
.test(/\.md$/)
.use("string-replace-loader")
.loader("string-replace-loader")
.options({
multiple: [{
search: '---(.*?)---',
replace: (match, p1, offset, string, groups) => `<div><p class="myclass">${p1.toUpperCase()}</p></div>`,
flags: 'ig'
},
{
search: ' ... ',
replace: (match, p1, p2, ..., pn, offset, string) => ` ... `,
flags: 'ig'
}
],
}, )
.end()
},
};

Handle Vue render errors locally

I am using Vue (server side rendered) with mjml to generate emails.
So I have something (overly simplified) like:
<mjml><mj-body>Hello {{ User.Name }}</mj-body></mjml>
If the model doesn't define User then Vue throws an error and the whole output is lost.
What I want to the output to be along the lines:
<mjml><mj-body>Hello <error>'User' is undefined</error></mj-body></mjml>
I have implemented Vue.config.errorHandler but that just tells me about the error -- there is no rendered output.
Anyway to implement the equivalent of an error handler around each variable substitution?
If you are using Vue version >= 2.5, you can use errorCaptured to create an ErrorBoundary
const ErrorBoundary = {
name: 'ErrorBoundary',
data: () => ({
error: false,
msg: ''
}),
errorCaptured (err, vm, info) {
this.error = true
this.msg = `${err.stack}\n\nfound in ${info} of component`
},
render (h) {
return this.error
? h('pre', this.msg)
: this.$slots.default[0]
}
}
and use this in your component
<error-boundary>
<mjml><mj-body>Hello {{ User.Name }}</mj-body></mjml>
</error-boundary>
If the application has any javascript error, it will be displayed on UI
Example on codepen
If you want to have more user-friendly error, you can customize ErrorBoundary to have fallback to another component. You can find out in this tutorial
Another good example of using errorHandler

Confirm URL using POM Nightwatch

New to nightwatch and js in general and I'm struggling to figure out how to validate a url using pom pattern. I know this is wrong. Any suggestions are greatly appreciated, including useful links for me to rtfm as I'm struggling to find robust examples of pom nightwatch.
test.js
module.exports = {
"tags": ['sanity'],
'confirm navigation to example.com' : function (client) {
var landingPage = client.page.landing();
landingPage.navigate();
landingPage.confirmOnLandingPage();
client.end();
}
};
page.js
var landingCommands = {
navigate:function(){
url: 'example.com/'
},
confirmOnLandingPage:function(){
this.expect.element('body')
.to.be.present.before(1000)
.url(function(result)
{
this.assert.equal(result.value, 'example.com/', 'On Landing Page.')
});
}
}
Running: confirm navigation to example.com
✖ TypeError:
this.expect.element(...).to.be.present.before(...).url is not a
function
at Page.confirmOnLandingPage (/Users/Home/Development/NWQA/pages/page.js:9:10)
at Object.confirm navigation to example.com (/Users/Home/Development/NWQA/tests/test.js:7:21)
FAILED: 1 errors (18ms)
After running .expect you break the Nightwatch command chain and start Expect.js chain so after you call this.expect.element('body').to.be.present.before(1000) you get Expect.js object and not Nightwatch browser object.
To fix just start a new chain and change this.url call to this.api.url since url() is not available within the page object:
confirmOnLandingPage: function(){
this.expect.element('body').to.be.present.before(1000);
this.api.url(function(result)
{
this.assert.equal(result.value, 'example.com/', 'On Landing Page.')
});
}
Update:
I just noticed you incorrectly declared URL for your page object. navigate is internal function, you only need to provide url property:
var landingCommands = {
url: 'example.com/',
...
};