How to dynamically create an EJS file from a database - mongodb

New web developer here; I am working with Mongo/Express/Node.
I am trying to make an e-commerce site where the admin will create new "categories" and add them to the database.
Whenever a new "category" is added to the database, I want a new EJS page to be created for that category (and all other categories in the database), that will then load all of the "products" that are of that category in that EJS page.
The category page should conform to a template; only changing its name and the products that are loaded into it.
Something like this:
<html>
<body>
<nav>
<h2>Categories</h2>
<div class="menu">
<a>Men</a>
<a>Women</a>
<a>Children</a>
</div>
</nav>
<% products.forEach(function(product) { %>
<% if (product.category === x) { %>
<h5><%= product.name %></h5>
<img src='someurl'>
<% } %>
<% }) %>
</body>
</html>
Thanks.

Luckily you don't need to dynamically create EJS files. You can use parameterised route matching and then return the right data for that category in the render function of the EJS page. That's the beauty of using dynamic web pages!
server.js
const express = require("express");
const app = express();
app.get("/example/:category", (req, res) => {
let { category } = req.params;
let products = /* search db for category that matches that "category" var */;
res.render("products.ejs", { products });
});
products.ejs
...
<% products.forEach(product => { %>
<h5><%= product.name %></h5>
<img src="<%= product.imageURL %>">
<% }); %>
...
If a user visits /example/food, for example, the category variable parameter will be equal to "food". You can then pass this in to the EJS render function.

Related

Accessing nested objects in a Meteor application using Blaze and Spacebars

I am doing a Meteor project for educational purpose. It's a blog with a post list page and a post detail page where logged-in users can add comments. Disclaimer: In the project I cannot use aldeed-simple schema and I won't use pub/sub methods. I am using iron-router and the accounts-password package for user authentication.
Step 1
I' ve set a basic application layout and a basic routing:
Router.route('/posts', function () {
this.render('navbar', {
to:"navbar"
});
this.render('post_list', {
to:"main"
});
});
Router.route('/post/:_id', function () {
this.render('navbar', {
to:"navbar"
});
this.render('post_detail', {
to:"main",
data:function(){
return Posts.findOne({_id:this.params._id})
}
});
Step 2
I ve created a post detail view which include a comment form. Find the code below:
<template name="post_detail">
<div class="container">
<p>Title: {{title}}</p>
<h5>Description: </h5>
<p>{{description}}</p>
<h4>Comments</h4>
<ul>
{{#each comment in comments}}
<li>{{comment}}</li>
{{/each}}
</ul>
{{>comments}}
</div>
</template>
<!-- Comments -->
<template name="comments">
<form class="js-add-comment-form">
<input type="text" id="comment" class="form-control"/>
<button type="submit" class="btn btn-default js-add-comment">Add a comment</button>
</form>
Step 3
I've added an event helper to the comment template in order to add user comments. The form takes the comment and the user Id. See code below:
Template.comments.events({
'submit .js-add-comment-form': function(event) {
event.preventDefault();
var comment = event.target.comment.value;
console.log(comment);
var user = Meteor.userId();
var email = Meteor.user().emails[0].address;
console.log(email)
console.log(user);
if (Meteor.user()){
Posts.update(
{_id: this._id},
{$push: {comments: {comments:comment, user}}});
event.target.comment.value = "";
}
}
});
Find below a post detail view in the browser:
I've inserted some comments but I cannot display it on the page. Instead I get and Object object for each comment and user id. If I go to the console this is my object:
How can I access those objects and display those field values(comments and user) on the page? Any help ? Thanks Sandro.
This is happening because {{comment}} is an object itself. Here's some sample code to get the message property showing:
<template name="post_detail">
<div class="container">
<p>Title: {{title}}</p>
<h5>Description: </h5>
<p>{{description}}</p>
<h4>Comments</h4>
<ul>
{{#each comment in comments}}
<li>{{comment.comments}} | {{emailForUser comment.user}}</li>
{{/each}}
</ul>
{{>comments}}
</div>
</template>
If you want to get the user's email address as per my example above, you'll want to add a template helper:
Template.post_detail.helpers({
emailForUser( id ) {
var user = Meteor.users.findOne({_id: id});
if( user ) {
return user.emails[0].address;
}
}
});

finding all users in a model in sails.js but not being able to add them to html for some reason

I have a "User" model and a "Lab" model.
When I create a new Lab object, I want to be able to pick up users from a list in a "select" html form...
So, in the view of lab/new.ejs I first display all the users in the User model database in the server console by doing:
<% User.find(function(err, users) {
_.each(users, function(user) {%>
<%console.log(user.name)%>
<% })
});%>
And it displays the list of all my users names correctly.
But when I try put all these users in a select form just below, the select form return empty with the following code ...
<div class="control-group">
<select multiple class="form-control" name="users">
<% User.find(function(err, musers) {
_.each(musers, function(muser) {%>
<option><%=muser.name%></option>
<% })
});%>
</select>
</div>
The .ejs view does not seem to see the content of the <%=muser.name%> variable while the server does... Any idea why?
You should not be running asynchronous code in your view. Find the users you want in your controller, then provide them to the view as view locals:
api/controllers/LabController.js:
new: function(req, res) {
User.find(function(err, users) {
if (err) {return res.serverError(err);}
return res.view({users: users});
});
}
views/lab/new.js:
<div class="control-group">
<select multiple class="form-control" name="users">
<% _.each(users, function(user) {%>
<option><%=user.name%></option>
<% }) %>
</select>
</div>

Confused on HTML for Mongo/Mongoose pagination

I currently have a collection with about 4,000 documents. I want to be able to paginate the results so users can only view 100 at a time.
I pretty much understand what to do on the server side when the search is submitted:
app.post('/browse/:page', function(req, res) {
var page = parseInt(req.params.page);
data = {};
data.page = page;
data.nextpage = page + 1;
data.prevpage = page - 1;
var filterOptions = _.transform(req.body, function(result, value, key) {
result[key] = new RegExp(value.replace(/[^A-Za-z 0-9!'-]/g,''), "i");
});
Model.find(filterOptions).sort('brand').skip((page-1)*100).limit(100).exec(function(err, results) {
//do stuff here with results
res.render('browse.ejs', data);
})
})
});
Now, on my view page this is what I have and where I think I am going about something quite wrong:
//header here
<div class="wrapper row2">
<div id="container" class="clear">
<section>
<div id="browse-wrap">
<div id="browse-left_col">
<h3>Browse by:</h3><br>
<form action="browse/1" method="post">
//form inputs and such here
<input type="submit" class="button" value="Browse">
</form>
</div>
<div id="browse-right_col">
//table of results here
<p style="text-align:right;">Next 100 →</p>
</div>
</div>
</section>
</div>
</div>
//footer here
Thanks for any help! I'm really new at this and appreciate it.
There's a few things I'd suggest:
Use app.get() instead of app.post(). You're retrieving information from the server and the app.get() method represents a HTTP GET, which in turn represents retrieval. The app.post() method as you know represents a HTTP POST, which should be used for adding information to the server.
Once you use app.get() you can represent the pages on the client as simple links to /browse/:page
Be mindful of edge cases where page=0, page=last. You probably don't want to show a link to previous when there's no previous page.
Using skip() has performance penalties when you skip over large number of documents. Just something to keep in mind.
Server:
app.get('/browse/:page', function(req, res) {
...
Model.find().sort('brand').skip((page-1)*100).limit(100).exec(function(err, results) {
res.render('browse.ejs', {data:data, results:results});
});
})
Client (using ejs as in your example):
// list data
<ul>
<% for(var i=0; i<results.length; i++) {%>
<li><%= results[i]._id %></li>
<% } %>
</ul>
// show prev/next page links
previous
next
Hope that helps.

Dynamically add a form_tag using jQuery in Rails 3.2

My app has a user dashboard that lists rules a user has created. Each rule can be added to a "ruleset". For each rule, I want to display a button that when clicked, loads a form that allows the user to add that rule to a ruleset.
I have the following code in my view to show each rule:
<% #rules.each do |rule| %>
<div class="rule-wrapper">
<div class="rule-content row">
<div class="span6">
<strong>Rule:</strong> <%= rule.description %>
</div>
<div class="span2">
<div class="add-to-ruleset-form">
</div>
</div>
</div>
......
The pertinent part of my Rules controller is as follows:
class RulesController < ApplicationController
before_filter :authenticate_user!
def add_rule_to_ruleset
#ruleset = Ruleset.find(params[:ruleset_id])
#rule = Rule.find(params[:rule_id])
#ruleset.rule << #rule
end
....
Lastly, here is what my dashboard.js.erb file looks like:
$('.add-to-ruleset').click(function(e){
e.preventDefault();
$('.add-to-ruleset-form').html('<%= escape_javascript(link_to render :partial => "add_to_ruleset_form", :locals => {:rule => rule}) %>')
});
I'd like that when a button ('add-to-ruleset') is clicked, that the partial is rendered. I've tried using "escape_javascript" helper in my js.erb file but it isn't passing the "rule" variable corrent. I've also tried to use jQuery's load(), but I don't know how to pass the "rule" instance variable to the partial using jQuery. Is there another way?

Silverstripe: LinkingMode with Custom Controller

I have following problem:
Some links that show up in the Menu (the children of "Portfolio") are links to custom controllers. Of course now the LinkingMode is not available for that Links. Thats a image of the Menu:
So the children of Portfolio (Website, Application, etc.) are actually Category-DataObjects, which do not have a SiteTree Representation. The Submenu of Portfolio is created via checking and looping for all found categories in the Database.
The menu creation looks like that:
<ul>
<% loop Menu(1) %>
<li class="$LinkingMode">
[$LinkingMode] $MenuTitle.XML
<% if Children %>
<ul class="secondary">
<% if ClassName == 'ProjectsPage' %>
<% loop $Top.Categories %> <!-- loop all found categories, every found item links to the custom category controller -->
<li class="$LinkingMode">$Name</li>
<% end_loop %>
<% else %>
<% loop Children %>
<li class="$LinkingMode"><span class="text">$MenuTitle.XML</span></li>
<% end_loop %>
<% end_if %>
</ul>
<% end_if %>
</li>
<% end_loop %>
</ul>
Every Category (Website, Mobile) in the Menu links to a custom controller, which looks basically like that:
class Category_Controller extends Page_Controller {
public function show($arguments) {
return $this; //there will be more code to display all projects of a category
}
}
I expect that I have to add some custom code for the Category_Controller which tells the Portfolio Page which linkingmode it has...
Many thx,
Florian
I´ve found good tips here:
http://www.ssbits.com/snippets/2009/extending-linkingmode-to-handle-controller-actions/
http://www.ssbits.com/tutorials/2010/dataobjects-as-pages-part-1-keeping-it-simple/
Thats the Category_Controller.php (a public var CategoryID is set there):
class Category_Controller extends Page_Controller {
public $CategoryID;
public function index($arguments) {
$slug = $arguments->param("Slug");
$category = Category::get()->filter(array('Slug' => $slug))->First();
$this->CategoryID = $category->ID;
}
}
DataObject Category (LinkingMode function checks if the current CategoryID set in the Controller equals the ID of the Category DateObject):
class Category extends DataObject {
public function LinkingMode(){
$categoryID = Controller::curr()->CategoryID;
return ($categoryID == $this->ID) ? 'current' : 'link';
}
}
In the template you can check then the linking mode as usual:
<% loop $Categories %>
<li class="$LinkingMode">$Name</li>
<% end_loop %>
Cheers,
Florian