Marko Dynamic Tag with Component - marko

I have a marko website where I have some dynamic components being called via a for loop:
/pages/note/index.marko
import layout from "../../layouts/base"
<${layout} title="test">
<for|card| of=input.cards>
<${card} />
</for>
</>
this is given a set of "notes" (just other marko files with the content) that I want to fill the page with dynamically based on the request (this is handled in the server just fine). It is loading these notes fine.
However, when I have the card marko file use a component, the component ony half works.
note1/index.marko
<math>5x+1=11</math>
math/index.marko
class {
onCreate() {
console.log("CREATED") // runs
}
onMount() {
console.log("MOUNTED") // doesn't run
// eventually I plan to run some math rendering code here
}
}
<span><${input.renderBody} /></span>
The issue is that the browser side of things never run. Also, I am getting this inexplicable error in the browser
edit: changed the rendering in the routing. somehow the error went away
routes.js
...
app.get("/note.html", async (req, res, next) => {
let title = req.query.title || "" // get the requested card
let dependencies = request(`./notes/${title}/dependencies.json`) || [] // get all of the linked cards to the requested card
let cards = [title, ...dependencies].map(note => request(`./notes/${note}`)) // get the marko elements for each card
// by this point, "cards" is a list with marko templates from the /notes/ directory
// render
let page = request(`./pages/note`, next)
let out = page.render({"title": title, "cards": cards}, res)
}
...
My file structure is set up like this:
server.js
routes.js
pages/
note/
index.marko
notes/
note1/
index.marko
note2...
components/
math/
index.marko
layouts/
base/
index.marko
Using: node, express, marko, & lasso.

Your custom tag of <math> is colliding with the native MathML <math> element, which is why you’re getting that error only in the browser.
Try naming it something else, like <Math> or <my-math>.

Related

React: add HTML from generic file path at server-side build time

The use case I'm trying to fulfill:
Admin adds SVG along with new content in CMS, specifying in the CMS which svg goes with which content
CMS commits change to git (Netlify CMS)
Static site builds again
SVG is added inline so that it can be styled and/or animated according to the component in which it occurs
Now - I can't figure out a clean way to add the SVG inline. My logic tells me - everything is available at build time (the svgs are in repo), so I should be able to simply inline the svgs. But I don't know how to generically tell React about an svg based on variables coming from the CMS content. I can import the svg directly using svgr/weback, but then I need to know the file name while coding, which I don't since it's coming from the CMS. I can load the svg using fs.readFileSync, but then the SVG gets lost when react executes client-side.
I added my current solution as an answer, but it's very hacky. Please tell me there's a better way to do this with react!
Here is my current solution, but it's randomly buggy in dev mode and doesn't seem to play well with next.js <Link /> prefetching (I still need to debug this):
I. Server-Side Rendering
Read SVG file path from CMS data (Markdown files)
Load SVG using fs.readFileSync()
Sanitize and add the SVG in React
II. Client-Side Rendering
Initial Get:/URL response contains the SVGs (ssr worked as intended)
Read the SVGs out of the DOM using HTMLElement.outerHTML
When React wants to render the SVG which it doesn't have, pass it the SVG from the DOM
Here is the code.
import reactParse from "html-react-parser";
import DOMPurify from "isomorphic-dompurify";
import * as fs from "fs";
const svgs = {}; // { urlPath: svgCode }
const createServerSide = (urlPath) => {
let path = "./public" + urlPath;
let svgCode = DOMPurify.sanitize(fs.readFileSync(path));
// add id to find the SVG client-side
// the Unique identifier is the filepath for the svg in the git repository
svgCode = svgCode.replace("<svg", `<svg id="${urlPath}"`);
svgs[urlPath] = svgCode;
};
const readClientSide = (urlPath) => {
let svgElement = document.getElementById(urlPath);
let svgCode = svgElement.outerHTML;
svgs[urlPath] = svgCode;
};
const registerSVG = (urlPath) => {
if (typeof window === "undefined") {
createServerSide(urlPath);
} else {
readClientSide(urlPath);
}
return true;
};
const inlineSVGFromCMS = (urlPath) => {
if (!svgs[urlPath]) {
registerSVG(urlPath);
}
return reactParse(svgs[urlPath]);
};
export default inlineSVGFromCMS;

Dispatch Express.js route based on first parameter?

I'm creating a CMS in node.js and Express. I allow users to create their own subsections in the site. A subsection can be a blog, a page or a forum. These sub-sections can be installed one level deep in the site url path, so for instance:
domain.com/custom-path-blog/
I would have to support the following url structure with express routes:
domain.com/custom-path-blog/ -> blog index
domain.com/custom-path-blog/page/5 -> list posts on page 5
domain.com/custom-path-blog/guides/ -> list posts that belong to guides category
domain.com/custom-path-blog/guides/this-is-a-post -> shows a post
I would also have to support other sub-sections with different url structures. I have to make a call to a database to check out what the first level in the url actually is before I can dispatch it to the appropriate route.
Since this is a saaas website I dont want to dynamically register the routes on my node process as I could end up having thousands of users with possibly millions of routes. This is not doable. I have to go to the database for that first chunk of information.
Once I know a sub section is a blog or a forum or a e-commerce store how do I send the url past that "custom-path-blog" to be processed by the appropriate express routing mechanism?
I'm starting to think this might be too complicated to do with express routes and I will have to do it by hand.
Thanks!
If you have already have 3 separated apps (page, blog, forum), and you want to launch it in 1 node process you can do this:
app.use('/page', pageApp);
app.use('/blog', blogApp);
app.use('/forum', forumApp);
express will strip out the first component of url for you.
In your case, the first component is customize by user, so you need to write a middleware for it:
function appSelector(req, res, next) {
var firstComponent = getFirtCompoent(req.url.pathname) // return page or blog ...
var userID = req.user.id;
detectAppForCurrentUser(firstCompoent, userID, function (type) {
if(type === 'page') {
removeFirstComponent(req);
return pageApp(req, res, next);
}
if(type === 'blog') {
removeFirstComponent(req);
return blogApp(req, res, next);
}
next(); // if not found continue with other routes
}
}
app.use(appSelector);
// TODO other routes here
there are many way to solve problem, but is it important rule: app.use, app.get are called on initialization phase only

How to serve rendered Jade pages as if they were static HTML pages in Node Express?

Usually you render a Jade page in a route like this:
app.get('/page', function(req, res, next){
res.render('page.jade');
});
But I want to serve all Jade pages (automatically rendered), just like how one would serve static HTML
app.use(express.static('public'))
Is there a way to do something similar for Jade?
"static" means sending existing files unchanged directly from disk to the browser. Jade can be served this way but that is pretty unusual. Usually you want to render jade to HTML on the server which by definition is not "static", it's dynamic. You do it like this:
app.get('/home', function (req, res) {
res.render('home'); // will render home.jade and send the HTML
});
If you want to serve the jade itself for rendering in the browser, just reference it directly in the url when loading it into the browser like:
$.get('/index.jade', function (jade) {
//...
});
https://github.com/runk/connect-jade-static
Usage
Assume the following structure of your project:
/views
/partials
/file.jade
Let's make jade files from /views/partials web accessable:
var jadeStatic = require('connect-jade-static');
app = express();
app.configure(function() {
app.use(jadeStatic({
baseDir: path.join(__dirname, '/views/partials'),
baseUrl: '/partials',
jade: { pretty: true }
}));
});
Now, if you start your web server and request /views/partials/file.html in browser you
should be able see the compiled jade template.
Connect-jade-static is good, but not the perfect solution for me.
To begin with, here are the reasons why I needed jade:
My app is a single page app, there are no HTMLs generated from templates at runtime. Yet, I am using jade to generate HTML files because:
Mixins: lots of repeated / similar code in my HTML is shortened by the use of mixins
Dropdowns: I know, lots of people use ng-repeat to fill the options in a select box. This is a waste of CPU when the list is static, e.g., list of countries. The right thing to do is have the select options filled in within the HTML or partial. But then, a long list of options makes the HTML / jade hard to read. Also, very likely, the list of countries is already available elsewhere, and it doesn’t make sense to duplicate this list.
So, I decided to generate most of my HTML partials using jade at build time. But, this became a pain during development, because of the need to re-build HTMLs when the jade file changes. Yes, I could have used connect-jade-static, but I really don’t want to generate the HTMLs at run time — they are indeed static files.
So, this is what I did:
Added a 'use' before the usual use of express.static
Within this, I check for the timestamps of jade and the corresponding html file
If the jade file is newer, regenerate the html file
Call next() after the regeneration, or immediately, if regeneration is not required.
next() will fall-through to express.static, where the generated HTML will be served
Wrap the ‘use’ around a “if !production” condition, and in the build scripts, generate all the HTML files required.
This way, I can also use all the goodies express.static (like custom headers) provides and still use jade to generate these.
Some code snippets:
var express = require('express');
var fs = require('fs')
var jade = require('jade');
var urlutil = require('url');
var pathutil = require('path');
var countries = require('./countries.js');
var staticDir = 'static'; // really static files like .css and .js
var staticGenDir = 'static.gen'; // generated static files, like .html
var staticSrcDir = 'static.src'; // source for generated static files, .jade
if (process.argv[2] != 'prod') {
app.use(‘/static', function(req, res, next) {
var u = urlutil.parse(req.url);
if (pathutil.extname(u.pathname) == '.html') {
var basename = u.pathname.split('.')[0];
var htmlFile = staticGenDir + basename + '.html';
var jadeFile = staticSrcDir + basename + '.jade';
var hstat = fs.existsSync(htmlFile) ? fs.statSync(htmlFile) : null;
var jstat = fs.existsSync(jadeFile) ? fs.statSync(jadeFile) : null;
if ( jstat && (!hstat || (jstat.mtime.getTime() > hstat.mtime.getTime())) ) {
var out = jade.renderFile(jadeFile, {pretty: true, countries: countries});
fs.writeFile(htmlFile, out, function() {
next();
});
} else {
next();
}
} else {
next();
}
});
}
app.use('/static', express.static(staticDir)); // serve files from really static if exists
app.use('/static', express.static(staticGenDir)); // if not, look in generated static dir
In reality, I have a js file containing not just countries, but various other lists shared between node, javascript and jade.
Hope this helps someone looking for an alternative.

How can I bootstrap models from express js into backbone at page load?

I have some data in a mongodb database and want to pass it to a backbone collection when I load the home page. One way of doing this would be to set up a node route like this:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.send(docs_string);
};
};
But this won't work because it won't render my jade template that pulls in the backbone code, it simply shows the JSON in plain text.
Alternatively, I could render my jade template passing in the data as a variable to jade:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.render('index', {
title: "Data",
docs_string: docs_string
})
});
};
Then in the jade template, have a script like this to add the users to my user collection:
script
var docs = !{docs_string};
var users = new app.Users();
_.each(docs, function(doc) {
var user = new app.User(doc);
users.add(user);
})
But this seems wrong, since I don't really want to pass the data to the jade template, I want to pass it to a backbone collection. Also, with this solution I don't know how to then include an underscore template (on the backbone side of things) into the page rendered by jade on the server side.
What is the standard way of passing data from a node server to a backbone collection?
Assuming your data is an object, you should convert it to string using JSON.stringify() and then insert in a page inside script tag, so your resulting HTML looks like this (I don't use Jade):
<script>
var data = {...}; // in template instead of {...} here should be the instruction to insert your json string
</script>
Then when the page loads, your script will be executed and the data will be available as a global variable in the browser so you can initialise backbone collection using it. This all is a good idea only to bootstrap your data on the first page load (to avoid extra request) and then use API to request data for this and other pages.
Check out Steamer, a tiny node / express module made for this exact purpose.
https://github.com/rotundasoftware/steamer

How to create global variables accessible in all views using Express / Node.JS?

Ok, so I have built a blog using Jekyll and you can define variables in a file _config.yml which are accessible in all of the templates/layouts. I am currently using Node.JS / Express with EJS templates and ejs-locals (for partials/layouts. I am looking to do something similar to the global variables like site.title that are found in _config.yml if anyone is familiar with Jekyll. I have variables like the site's title, (rather than page title), author/company name, which stay the same on all of my pages.
Here is an example of what I am currently doing.:
exports.index = function(req, res){
res.render('index', {
siteTitle: 'My Website Title',
pageTitle: 'The Root Splash Page',
author: 'Cory Gross',
description: 'My app description',
indexSpecificData: someData
});
};
exports.home = function (req, res) {
res.render('home', {
siteTitle: 'My Website Title',
pageTitle: 'The Home Page',
author: 'Cory Gross',
description: 'My app description',
homeSpecificData: someOtherData
});
};
I would like to be able to define variables like my site's title, description, author, etc in one place and have them accessible in my layouts/templates through EJS without having to pass them as options to each call to res.render. Is there a way to do this and still allow me to pass in other variables specific to each page?
After having a chance to study the Express 3 API Reference a bit more I discovered what I was looking for. Specifically the entries for app.locals and then a bit farther down res.locals held the answers I needed.
I discovered for myself that the function app.locals takes an object and stores all of its properties as global variables scoped to the application. These globals are passed as local variables to each view. The function res.locals, however, is scoped to the request and thus, response local variables are accessible only to the view(s) rendered during that particular request/response.
So for my case in my app.js what I did was add:
app.locals({
site: {
title: 'ExpressBootstrapEJS',
description: 'A boilerplate for a simple web application with a Node.JS and Express backend, with an EJS template with using Twitter Bootstrap.'
},
author: {
name: 'Cory Gross',
contact: 'CoryG89#gmail.com'
}
});
Then all of these variables are accessible in my views as site.title, site.description, author.name, author.contact.
I could also define local variables for each response to a request with res.locals, or simply pass variables like the page's title in as the optionsparameter in the render call.
EDIT: This method will not allow you to use these locals in your middleware. I actually did run into this as Pickels suggests in the comment below. In this case you will need to create a middleware function as such in his alternative (and appreciated) answer. Your middleware function will need to add them to res.locals for each response and then call next. This middleware function will need to be placed above any other middleware which needs to use these locals.
EDIT: Another difference between declaring locals via app.locals and res.locals is that with app.locals the variables are set a single time and persist throughout the life of the application. When you set locals with res.locals in your middleware, these are set everytime you get a request. You should basically prefer setting globals via app.locals unless the value depends on the request req variable passed into the middleware. If the value doesn't change then it will be more efficient for it to be set just once in app.locals.
You can do this by adding them to the locals object in a general middleware.
app.use(function (req, res, next) {
res.locals = {
siteTitle: "My Website's Title",
pageTitle: "The Home Page",
author: "Cory Gross",
description: "My app's description",
};
next();
});
Locals is also a function which will extend the locals object rather than overwriting it. So the following works as well
res.locals({
siteTitle: "My Website's Title",
pageTitle: "The Home Page",
author: "Cory Gross",
description: "My app's description",
});
Full example
var app = express();
var middleware = {
render: function (view) {
return function (req, res, next) {
res.render(view);
}
},
globalLocals: function (req, res, next) {
res.locals({
siteTitle: "My Website's Title",
pageTitle: "The Root Splash Page",
author: "Cory Gross",
description: "My app's description",
});
next();
},
index: function (req, res, next) {
res.locals({
indexSpecificData: someData
});
next();
}
};
app.use(middleware.globalLocals);
app.get('/', middleware.index, middleware.render('home'));
app.get('/products', middleware.products, middleware.render('products'));
I also added a generic render middleware. This way you don't have to add res.render to each route which means you have better code reuse. Once you go down the reusable middleware route you'll notice you will have lots of building blocks which will speed up development tremendously.
For Express 4.0 I found that using application level variables works a little differently & Cory's answer did not work for me.
From the docs: http://expressjs.com/en/api.html#app.locals
I found that you could declare a global variable for the app in
app.locals
e.g
app.locals.baseUrl = "http://www.google.com"
And then in your application you can access these variables & in your express middleware you can access them in the req object as
req.app.locals.baseUrl
e.g.
console.log(req.app.locals.baseUrl)
//prints out http://www.google.com
In your app.js you need add something like this
global.myvar = 100;
Now, in all your files you want use this variable, you can just access it as myvar
One way to do this by updating the app.locals variable for that app in app.js
Set via following
var app = express();
app.locals.appName = "DRC on FHIR";
Get / Access
app.listen(3000, function () {
console.log('[' + app.locals.appName + '] => app listening on port 3001!');
});
Elaborating with a screenshot from #RamRovi example with slight enhancement.
you can also use "global"
Example:
declare like this :
app.use(function(req,res,next){
global.site_url = req.headers.host; // hostname = 'localhost:8080'
next();
});
Use like this:
in any views or ejs file
<%
console.log(site_url);
%>
in js files
console.log(site_url);
With the differents answers, I implemented this code to use an external file JSON loaded in "app.locals"
Parameters
{
"web": {
"title" : "Le titre de ma Page",
"cssFile" : "20200608_1018.css"
}
}
Application
var express = require('express');
var appli = express();
var serveur = require('http').Server(appli);
var myParams = require('./include/my_params.json');
var myFonctions = require('./include/my_fonctions.js');
appli.locals = myParams;
EJS Page
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title><%= web.title %></title>
<link rel="stylesheet" type="text/css" href="/css/<%= web.cssFile %>">
</head>
</body>
</html>
Hoping it will help
What I do in order to avoid having a polluted global scope is to create a script that I can include anywhere.
// my-script.js
const ActionsOverTime = require('#bigteam/node-aot').ActionsOverTime;
const config = require('../../config/config').actionsOverTime;
let aotInstance;
(function () {
if (!aotInstance) {
console.log('Create new aot instance');
aotInstance = ActionsOverTime.createActionOverTimeEmitter(config);
}
})();
exports = aotInstance;
Doing this will only create a new instance once and share that everywhere where the file is included. I am not sure if it is because the variable is cached or of it because of an internal reference mechanism for the application (that might include caching). Any comments on how node resolves this would be great.
Maybe also read this to get the gist on how require works:
http://fredkschott.com/post/2014/06/require-and-the-module-system/

Resources