Rendering elements classname in client-side without JS framework? (EJS) - node.js

Let's say I have an app which shows to the users a list of existing hobbies.
Each hobby has a category, stored in the db.
I want every hobby element to have its background color - dependent on its category.
I want to implement this with appending specific class to each element.
Basic example code:
Server
app.get("/hobbies", (req, res) => {
const hobbies = Hobby.getAllHobbies();
res.render("hobbies", hobbies);
});
Client (EJS)
<% hobbies.forEach(hobby => { %>
<div class=""><%= hobby.name %></div>
<% }); %>
What is the best way to append to each div a class depending of hobby.category?
I know its easily possible in React, but I don't want to use any framework for now.

If your classname is not the same as the category but is based on it, then you just need to pass a lookup object to your template.
Server
const categories_classnames = {
lookup: {
swimming: 'div-swim',
biking: 'div-bike',
painting: 'div-paint',
// ...
}
};
app.get("/hobbies", (req, res) => {
const hobbies = Hobby.getAllHobbies();
// Alternatively, `locals = { ...hobbies, ...categories_classnames }`
const locals = Object.assign({}, hobbies, categories_classnames);
res.render("hobbies", locals);
});
Client
<% hobbies.forEach(hobby => { %>
<div class="<%= lookup[hobby.category] %>"><%= hobby.name %></div>
<% }); %>

Related

Form Select onChange Sends Data/Info to Query on Server

I'm using Nodejs, Express, and EJS.
Here's what works...
I can use an unordered list of hyperlinks and send the info/variable via req.params this way...
db.ejs code
<ul>
<% dbTitle.forEach(function(dbTitle){ %>
<li><%= dbTitle.dbTitle %></li>
<% }) %>
</ul>
server.js code
app.get('/db/:dbTitle', async (req, res) => {
const {dbTitle} = req.params;
console.log(dbTitle);
try {
const tabTitleResult = await session.run(`MATCH (db:Database {Title: $dbTitle})-->(t:Table)-->(tv:TableVersion)
Where NOT (tv)<--(:InformationAsset)
RETURN db.Title as dbTitle, tv.Title as tabTitle Order By db.Title, tv.Title ASC`, {dbTitle});
const tabTitleArr = tabTitleResult.records.map(({_fields}) => {
return {dbTitle:_fields[0],tabTitle:_fields[1]};
});
res.render('table.ejs', { tabTitle: tabTitleArr});
//console.log(tabTitleArr)
} catch(e) {
console.log("Something went wrong", e)
}
});
everything from above displays nicely on this page...
table.ejs code
<table>
<tr>
<th>Database-Title</th>
<th>Table-Title</th>
</tr>
<% tabTitle.forEach(function (tabTitle){ %>
<tr>
<td><%= tabTitle.dbTitle %></td>
<td><%= tabTitle.tabTitle %></td>
<% }) %>
</tr>
</table>
Here's what doesn't work...
Instead of an unordered list of hyperlinks, I would prefer to have a dropdown select, however my code doesn't work when I try to use a form select option method to send the info/variable via req.body...
db.ejs code
<form method="post" action="/db">
<label>Database Name</label><br>
<select name="dbTitle" onchange="this.form.submit();">
<option selected disabled> -- select an option --
<% dbTitle.forEach(function(dbTitle){ %>
<option name="dbTitle" value="<%= dbTitle.dbTitle %>"><%= dbTitle.dbTitle %></option>
<% }) %>
</option>
</select>
</form>
(Note: I am aware of how strange the nested options seem, this is required to force the --select an option-- option to appear first, removing the nesting with only the one option with data does not help.
Also, you'll note that I'm adding name="dbTitle" on more than one element in a desperate attempt to make something work, I believe it should only be on the select element.
Last, I'm also trying to send any info/variable via value="<%= dbTitle.dbTitle %>.)
server.js code
app.post('/db/:dbTitle', async (req, res) => {
const {dbTitle} = req.body;
console.log(dbTitle);
try {
const tabTitleResult = await session.run(`MATCH (db:Database {Title: $dbTitle})-->(t:Table)-->(tv:TableVersion)
Where NOT (tv)<--(:InformationAsset)
RETURN db.Title as dbTitle, tv.Title as tabTitle Order By db.Title, tv.Title ASC`, {dbTitle});
const tabTitleArr = tabTitleResult.records.map(({_fields}) => {
return {dbTitle:_fields[0],tabTitle:_fields[1]};
});
res.render('table.ejs', { tabTitle: tabTitleArr});
//console.log(tabTitleArr)
} catch(e) {
console.log("Something went wrong", e)
}
});
From here, when I run and then select from the dropdown, I receive an error of Cannot POST /table, and nothing shows in my console.log(dbTitle);, so I'm assuming no variable is being sent from my form to the server.
From what I've gathered in using a form vs ul li hyperlinks, there are some differences where the form needs to have method="post", and the server needs to be app.post with req.body instead of req.params. Or maybe this is incorrect?
Thank you for any help you can share.
I figured it out, here's what I needed to do.
Everything was fine on my client db.ejs.
In my server.js, I needed to change app.post('/auradbtable/:dbTitle' to app.post('/auradbtable?:dbTitle'... change the '/' to '?'.
And using const {dbTitle}=req.body; is correct.

I have an array of Questions and Answers on serverside. How can I render these one by one in EJS template on click?

My route:
app.get('/test', (req,res)=>{
res.render('index.ejs',{qData: [{Q1},{Q2},...]});
})
How to render this qData on client side one by one on clicks? Suppose I have two buttons "Next" and "Previous" for rendering next and previous questions respectively.
You need to pass information back and forth between client(browser/EJS rendered template) and server (node/route) to keep track of which question is being viewed. For example:
Route:
const questions = [Q1, Q2, Q3, ...];
// The URL for this route is like:
// http://localhost:3000/test/0
// Or:
// http://localhost:3000/test/1
// Where the last part tells us which question the user is on.
app.get('/test/:page', (req, res) => {
// Pull the page parameter out of the request.
let page = Number.parseInt(req.params.page) || 0;
res.render('index.ejs', {
qData: questions[page],
page,
pageMax: questions.length - 1
});
});
Template:
<!-- Render your Question Data however you need to: -->
<div>
<%- JSON.stringify(qData) %>
</div>
<!-- Pagination -->
<% if (page > 0) { %>
Prev
<% } %>
<% if (page < pageMax) { %>
Next
<% } %>

Client side and Server side rendering of ejs template

I always wanted to learn NodeJS to be able to run the same code on server and client side.
I am using NodeJS with Express and EJS.
So. I have a .ejs page with lot's of HTML, JS, CSS and a small bit with template. For the sake of justice let it be like this:
the_list-->some.ejs
<ul>
<% for(i=0;i>the_list.length;i++) { %>
<li>the_list[i]</li>
<% } %>
</ul>
After some rendering on the server we have a perfect list.
So.
Now I want to rerender it on the client. I made some ajax request and now I have new items in the_list. What is the right way?
As per ejs templates documentation
var template = new EJS({
text: `
<ul>
<% for(i = 0; i < the_list.length; i++) { %>
<li>the_list[i]</li>
<% } %>
</ul>
`
});
var html = template.render({ the_list: data });
document.getElementById('list-wrapper').innerHTML = html;
<div id="output"></div>
<script src="/assets/js/ejs.js"></script>
<script>
let blogPosts = [
{
title: 'Perk is for real!',
body: '...',
author: 'Aaron Larner',
publishedAt: new Date('2016-03-19'),
createdAt: new Date('2016-03-19')
},
{
title: 'Development continues...',
body: '...',
author: 'Aaron Larner',
publishedAt: new Date('2016-03-18'),
createdAt: new Date('2016-03-18')
},
{
title: 'Welcome to Perk!',
body: '...',
author: 'Aaron Larner',
publishedAt: new Date('2016-03-17'),
createdAt: new Date('2016-03-17')
}
];
var html = ejs.render(`<% for(let i = 0; i < posts.length; i++) { %>
<article>
<h2><%= posts[i].title %></h1>
<p><%= posts[i].body %></p>
</article>
<% } %>`, {posts: blogPosts});
// Vanilla JS:
document.getElementById('output').innerHTML = html;
</script>
download ejs.js or ejs.min.js from latest version
Sure, EJS works on the client. You can trivially keep the template in a string variable or apply EJS to user-provided input, but more likely, you'll want to store a template in a script (which can be in an external file) or use fetch to grab your template from another file on demand.
Using a template in a <script> is straightforward:
const people = ["geddy", "neil", "alex"];
const template = document
.querySelector("#template")
.innerText;
document.querySelector("#output")
.innerHTML = ejs.render(template, {people});
<!-- could be an external file -->
<script id="template" type="text/template">
<%= people.join(", "); %>
</script>
<div id="output"></div>
<script src="https://unpkg.com/ejs#3.1.8/ejs.min.js"></script>
For fetch, I'll mock the response so it'll be runnable in a snippet:
// mock fetch for illustrative purposes;
// its response content would be another file
fetch = async url => ({text: async () => '<%= people.join(", "); %>'});
fetch("/your-template")
.then(res => res.text())
.then(template => {
const people = ["geddy", "neil", "alex"];
document.querySelector("#output").innerHTML =
ejs.render(template, {people});
});
<script src="https://unpkg.com/ejs#3.1.8/ejs.min.js"></script>
<div id="output"></div>
If this seems like too much heavy lifting, you can bury the fetch in a helper function, or go a step further and pick an attribute for each URL, then plug everything in with a call to a library function you can abstract away from the main code. A simple example:
// mock fetch for illustrative purposes;
// its response content would be in other files
const responses = {
"/template.ejs": "<%= 42 %>",
"/other-template.ejs": "<%= 43 %>",
};
fetch = async url => ({text: async () => responses[url]});
[...document.querySelectorAll("[data-template]")]
.forEach(e => {
fetch(e.getAttribute("data-template"))
.then(res => res.text())
.then(template => {
e.innerHTML = ejs.render(template);
});
});
<script src="https://unpkg.com/ejs#3.1.8/ejs.min.js"></script>
<div data-template="/template.ejs"></div>
<div data-template="/other-template.ejs"></div>
Either way, keep in mind that JS will run after the static HTML is parsed and the DOM loads. This means the data won't appear all in one fully-formed piece as when using EJS on the server. Network errors are possible.
See also using ejs partials from express client side. If you want to mock the include function, the problem is that the fetch call is asynchronous but the include function isn't. EJS offers an include callback that seems like it offers an opportunity to pull in an external file, but it's purely synchronous and won't await any promises you return. How to work around this best depends on your use case.
This should work, looks like your problem was the relational operator '>' because it will never output something.
<ul>
<% for(var i=0; i<the_list.length; i++) { %>
<li>
<a>
<%= the_list[i]%>
</a>
</li>
<% } %>
</ul>

MEAN correct way to refactor ejs express code

I've created a website with express and ejs, pulling JSON in from a file in a local file. I'd like to pass the data in from a mongo database and display with an Angular view. I have a good amount of knowledge in express, angular and mongo. Putting it all together and separating the express tasks from the angular tasks has me hung up a bit.
My app is working over in Heroku at http://healingcenter.herokuapp.com/services/ this is the page I'm unclear on how to separate the express and angular tasks.
It is currently set up in express, this was before I researched angular and the MEAN stack. below is the route config.
router.get('/services', function(req, res) {
var myServices = [];
var entireList = [];
entireList = appdata.services;
appdata.services.forEach(function(item) {
myServices = myServices.concat(item.title);
});
res.render('services', {
title: 'Services',
services: myServices,
list: entireList,
page: 'servicesList'
});
});
/* GET services detail page. */
router.get('/services/:servicesid', function(req, res) {
var myServices = [];
var entireList = [];
appdata.services.forEach(function(item){
if (item.shortname == req.params.servicesid){
entireList.push(item);
myServices = myServices.concat(item.title);
}
});
res.render('services', {
title: 'Services',
services: myServices,
list: entireList,
page: 'servicesDetail'
});
});
This is pulling in the same data.json file from the app.js file with this line of code.
app.locals.appdata = require('./data.json');
The page variable is the important part. An if else statement is customizing which data is brought in. That code is here:
<div class="services">
<% list.forEach(function(item){ %>
<article class="service group">
<% if (page!= 'servicesDetail') { %>
<h3><%= item.title %></h3>
<p><%- item.summary %></p>
more
<% }else{ %>
<a class="back" href="/services/#"><i class="fa fa-chevron-circle-left"></i> back</a>
<h3><%= item.title %></h3>
<p><%- item.description %></p>
<% } %>
</article>
<% }); %>
</div>

Create jqueryMobile page dynamically based on the url

I am creating an application to get some experience in jQuery Mobile and backbone. I have made a "restful" API with node.js that handles the data I need. It works fine with all my static pages I made in index.html. But when I need to create a page with data from a certain id I am a bit lost.
For example when I want to display all items(/items) I have a data-role=page with id items that list all items, but when I need to go to a detailed page for each item (/items/1) i want to create that details page whenever a user wants details on an item, in other words when a user visit the url spots#3 for example.
Is this possible?
my router: the model gives me all data i want
Spoter.Router = Backbone.Router.extend({
routes: {
"": "",
"spot#:id": "spotDetails"
},
//Details on a certain spot with id
spotDetails: function(id) {
var spotDetailsContentDiv = Spoter.spotDetailsContent;
spotDetailsContentDiv.empty();
var spot = new Spoter.spotModel({id: id});
spot.fetch({
successCallback: function(data) {
var spotDetailsView = new Spoter.spotDetailsView({
model: data
});
spotDetailsContentDiv.html(spotDetailsView.render().el);
}
});
}
});
View:
Spoter.spotDetailsView = Backbone.View.extend({
render:function () {
this.$el.html(this.template(this.model));
return this;
}
});
Template with underscore
<ul data-role="listview" data-theme="c" data-inset="true">
<li>
<a href="#">
<h1><%= this.model.name %></h1>
<p><%= this.model.description %></p>
</a>
</li>
</ul>

Resources