Access static files (js/css..) HapiJS, EJS, NodeJS - node.js

When using routes, like (http://localhost:port/foo/bar), in the view, in my case an ejs, do i need to declare the full path in the view, to load css/js/images ?
Vide: https://github.com/poeticninja/hapi-ninja
hapi-ninja/server/base/index.js
...
{
method: 'GET',
path: '/foo/bar',
config: {
handler: function(request, reply){
reply.view('about', {
title: 'Super Informative About Page'
});
},
id: 'about'
}
},
...
ex:
foot.ejs
<!-- Include the JS -->
<% if (assets.js.length) { %>
<% assets.js.forEach(function(js){ %>
<script src="<%=js%><%=version.cache%>"></script>
<% }) %>
<% } %>
Cause, every time i try to load, it gets the relative path (http://localhost:port/foo/bar/js/script.js).

As described by the creator himself:
First solution:
#poeticninja commented 4 hours ago
In your assets file make the paths /js/script.js and not js/script.js. That will fix your problem.
Other 'solution':
In the assets.js
// assets to be used by the 'hapi-assets' module based on process.env.NODE_ENV
module.exports = {
development: {
js: ['js/jquery-2.1.4.js', 'js/bootstrap.js'],
css: ['css/bootstrap.css', 'css/bootstrap-theme.css', 'css/3-col-portfolio.css'],
host: 'http://development:PORT/'
},
production: {
// TODO: Add the resources minified.
js: ['js/jquery-2.1.4.js', 'js/bootstrap.js'],
css: ['css/bootstrap.css', 'css/bootstrap-theme.css', 'css/3-col-portfolio.css'],
host: 'http://production:PORT/'
}
}
and in the view:
<script src="<%=assets.host%><%=js%><%=version.cache%>"></script>
But, with the initial '/', is better.

Related

nuxt.js. mode: spa. problem in not root path on production

I am a Japanese not good at English sorry.
I have a nuxt project like this.
The mode is spa.
Directory construction
pages - index.vue
index
|_ child.vue
|_ index.vue
pages/index.vue
<template>
<div>
{{ title }}
<router-view />
</div>
</template>
<script>
export default {
computed: {
title () {
let title = ''
if (this.$route.path === '/') {
title = 'Parent'
} else if (this.$route.path === '/child') {
title = 'Child'
}
return title
}
}
}
</script>
When I build(or generate), you can get static file of child/index.html.
I upload inside the dist to server.
But if I access to http://deployedrootpath/child, the computed property doesn't work.
I think this happens because these static files are created before created hook.
It can only know returned value from asyncData hook.
So I did like this.
middleware/redirect.js
export default function ({ route, redirect }) {
if (route.path === '/child/') {
// I need to set some params because of navigation duplication error of vue-router.
redirect('/child', { params: { 'redirect': true } })
}
}
pages/index/child.vue
<template>
<div>
child
</div>
</template>
<script>
export default {
middleware: 'redirect'
}
</script>
Actually it worked but I know this is a terrible way.
And even if there is no way except for this, I want to at least hide params from redirected url.
Please help me.
I solved.
nuxt.config.js
trailingSlash: true
This make the static files path and $route.path same.
https://nuxtjs.org/api/configuration-router/#trailingslash

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>

Session.set inside lib directory in Meteor

I have my iron-router code in my top-level lib directory in a file called routes.js. In that file I make calls to the front-end Session variable, specifically inside my onBeforeAction method like so:
Router.map(function () {
this.route('homeTemplate',{
name: 'homeTemplate',
where: 'client',
path: '/',
load: function() {
$('html, body').animate({
scrollTop: 0
}, 400);
return $('.content').hide().fadeIn(1000);
},
waitOn: function() {
return Meteor.subscribe(COLLECTION_NAMES.PlayerCollection.value);
},
onBeforeAction: function(){
Session.set('active_menu_option','home');
this.next();
},
onAfterAction: function(){
},
data: function(){
return {active_menu_option: {'home':'active'}};
},
yieldTemplates: {
'jumbotronTemplate': {
to: 'jumbotron'
}
}
});
});
Is it kosher to call/use the Session variable in this setting? I seem to be getting wonky/non-deterministic behavior.
I'm not sure about the Session usage, but if all you want is setting active menuitems depending on route location, then maybe this will help: meteor add zimme:iron-router-active
Usage:
<nav>
<ul>
<li class="{{isActiveRoute regex='dashboard'}}">...</li>
<li class="{{isActiveRoute regex='dashboard|index'}}">...</li>
<li class="{{isActiveRoute regex='users' className='on'}}">...</li>
<li class="{{isActivePath regex='products'}}">...</li>
{{#if isActiveRoute regex='index'}}
<li>...</li>
{{/if}}
<li class="{{isNotActiveRoute regex='dashboard'}}">...</li>
</ul>
</nav>
This helper uses regex which means strings like this will work too.
'^dashboard$' // Exact match for 'dashboard'
'^product' // Begins with 'product'
'list$' // Ends with 'list'

Passing an object to client in node/express + ejs?

I have a pretty large object that I need to pass to a function in a client script. I have tried using JSON.stringify, but have run into a few issues with this approach - mostly performance related. Is it possible to do something like this in ejs?
app.get('/load', function(req, res) {
var data = {
layout:'interview/load',
locals: {
interview: '',
data: someLargeObj
}
};
res.render('load', data);
});
And in my client script, I would pass this object to a function like so
<script type="text/javascript">
load(<%- data %>); // load is a function in a client script
</script>
When I try this I get either
<script type="text/javascript">
load();
</script>
or
<script type="text/javascript">
load([Object object]);
</script>
In Node.js:
res.render('mytemplate', {data: myobject});
In EJS:
<script type='text/javascript'>
var rows =<%-JSON.stringify(data)%>
</script>
SECURITY NOTE : Don't use this to render an object with user-supplied data. It would be possible for someone like Little Bobby Tables to include a substring that breaks the JSON string and starts an executable tag or somesuch. For instance, in Node.js this looks pretty innocent...
var data = {"color": client.favorite_color}
but could result in a client-provided script being executed in user's browsers if they enter a color such as:
"titanium </script><script>alert('pwnd!')</script> oxide"
If you need to include user-provided content, please see https://stackoverflow.com/a/37920555/645715 for a better answer using Base64 encoding
That is the expected behavior. Your template engine is trying to create a string from your object which leads to [Object object]. If you really want to pass data like that I think you did the correct thing by stringifying the object.
If you are using templating, then it would be much better to get the values in the template, for example whether user is signed in or not. You can get the send local data using
<script>
window.user = <%- JSON.stringify(user || null) %>
</script>
From the server side code, you are sending user data.
res.render('profile', {
user: user.loggedin,
title: "Title of page"
});
Think there's a much better way when passing an object to the ejs , you dont have to deal with JSON.stringfy and JSON.parse methods, those are a little bit tricky and confusing. Instead you can use the for in loop to travel the keys of your objects, for example:
if you have an object like such hierarchy
{
"index": {
"url": "/",
"path_to_layout": "views/index.ejs",
"path_to_data": [
"data/global.json",
{
"data/meta.json": "default"
}
]
},
"home": {
"url": "/home",
"path_to_layout": "views/home/index.ejs",
"path_to_data": [
"data/global.json",
{
"data/meta.json": "home"
}
]
},
"about": {
"url": "/about",
"path_to_layout": "views/default.ejs",
"path_to_data": [
"data/global.json",
{
"data/meta.json": "about"
}
]
}
}
On the EJS side you can loop yourObject like this;
<% if ( locals.yourObject) { %>
<% for(key in yourObject) { %>
<% if(yourObject.hasOwnProperty(key)) { %>
<div> <a class="pagelist" href="<%= yourObject[key]['subkey'] %>"><%= key %></a></div>
<% } %>
<% } %>
<% } %>
For this example [key] can take 'index','home' and 'about' values and subkey can be any of it's children such as 'url','path_to_layout','path_to_data'
What you have is a result like this
[{'re': 'tg'}]
You actually need to loop it. See javascript while loop https://www.w3schools.com/js/js_loop_while.asp
Then, render it in your front end with ejs... i can't help on that, i use hbs

Nested resources in railway.js: Says cannot POST

I am trying out an MVC framework called railway.js (which sits on top of Node, Express, Mongoose, and Mongo).
I'm trying to get nested resources to work. I did the following scaffolding commands:
railway g scaffold user name email description
railway g scaffold setup title description
Then I changed the routes.js file to:
exports.routes = function (map) {
map.resources('users',function(user) {
user.resources('setups');
});
});
Doing railway r gives what I hoped for:
user_setups GET /users/:user_id/setups.:format? setups#index
user_setups POST /users/:user_id/setups.:format? setups#create
new_user_setup GET /users/:user_id/setups/new.:format? setups#new
edit_user_setup GET /users/:user_id/setups/:id/edit.:format? setups#edit
user_setup DELETE /users/:user_id/setups/:id.:format? setups#destroy
user_setup PUT /users/:user_id/setups/:id.:format? setups#update
user_setup GET /users/:user_id/setups/:id.:format? setups#show
users GET /users.:format? users#index
users POST /users.:format? users#create
new_user GET /users/new.:format? users#new
edit_user GET /users/:id/edit.:format? users#edit
user DELETE /users/:id.:format? users#destroy
user PUT /users/:id.:format? users#update
user GET /users/:id.:format? users#show
When I start up the server, add a user (happens to be 4e4b61e39f0d60d834000002), then go to http://localhost:3000/users/4e4b61e39f0d60d834000002/setups/new, it says I "cannot POST".
What am I missing? What's a good debugging approach?
I also tried adding an element into the UserSchema object: setups: [SetupSchema]. (Shouldn't we have to do this?)
Thanks in advance.
in your setup_controller:
before(loaduser);
...
function loadUser () {
User.findById(req.params.user_id, function (err, user) {
if (err || !user) {
redirect(path_to.users);
} else {
// this is where we make the actual user object accessible inside the view templating
this.user = user;
next();
}
}.bind(this));
}
in your setup/form.jade:
- form_for( setup, { method: 'POST', action: path_to.user_setups(user) }, function(form){
//layout your form
- });
Look at file app/views/setups/new.ejs:
<h1>New setup</h1>
<% form_for(setup, {action: path_to.setups, method: 'POST', id: "setup_form"}, function (form) { %>
<%- partial('setups/form.ejs', {locals: {form: form, setup: setup}}) %>
<%- form.submit('Create setup') %> or
<%- link_to('Cancel', path_to.setups) %>
<% });%>
It refers to not existing route path_to.setups, you have to change it to correct route path_to.user_setups:
<h1>New setup</h1>
<% form_for(setup, {action: path_to.user_setups(user), method: 'POST', id: "setup_form"}, function (form) { %>
<%- partial('setups/form.ejs', {locals: {form: form, setup: setup}}) %>
<%- form.submit('Create setup') %> or
<%- link_to('Cancel', path_to.setups) %>
<% });%>
So, you will POST to /users/.../setups instead of POST to /users/.../setups/new.
Please note that you need pass user as first argument of path_to helper, so railway will build correct route for you.

Resources