I have a simple page with head, menu, content and footer. I need to divide them into separate files. After reading through express documentation i (created 4 templates and) wrote something like this:
app.get('/', function(req, res) {
var response = [null, null, null, null]
, everyNotNull = function(elem) {
return (elem !== null);
}, sendResponse = function(type, str) {
switch (type) {
case 'head' : response[0] = str; break;
case 'menu' : response[1] = str; break;
case 'content' : response[2] = str; break;
case 'footer' : response[3] = str; break;
}
if (response.every(everyNotNull)) {
res.send(response.join(''));
}
};
res.partial('head', {'title' : 'page title'}, function(err, str) {
sendResponse('head', str);
});
res.partial('menu', {'active' : '/'}, function(err, str) {
sendResponse('menu', str);
});
res.partial('index', {'title' : 'Home'}, function(err, str) {
sendResponse('content', str);
});
res.partial('footer', {'nowdate' : (new Date()).getFullYear()}, function(err, str) {
sendResponse('footer', str);
});
});
Though it works it seems a bit dirty to me. Is there a better way to use partial templates?
You were right to suspect something was missing, you're doing unnecessary work there.
Express will stitch the templates together for you, just call res.render() and the name of the view you want to call. The layout and partials should get pulled in automatically.
In my apps I usually use partials as below. Just replace references to EJS with whichever template engine you're using (Jade, moustache, etc):
./lib/app.js
app.get('/', function(req, res) {
var model = {
layout:'customLayout', // defaults to layout.(ejs|jade|whatever)
locals:{
foo:'bar'
}
};
res.render('index',model);
});
./views/layout.ejs
<html>
<head><%- partial('partials/head') %></head>
<body>
<%- partial('partials/menu') %>
<%- body %>
<%- partial('partials/footer') %>
</body>
</html>
./views/index.ejs
<h1>Index page</h1>
./views/partials/menu.ejs
<div><a href='... </div>
./views/partials/head.ejs
<script>...</script>
etc.
Related
It's a little strange but I cannot think of a better way to get it done.
First of all this is my code:
The router to get the view in the first place
router.get('/', function(req, res, next) {
Account.findOne(
{
_id: req.user._id,
},
function(err, acc) {
if (err) {
console.log(err);
}
// console.log(acc.websites);
res.render('reports/index', {
title: 'Reports!',
websites: acc.websites,
user: req.user,
});
}
);
});
The view:
<% include ./../partials/header.ejs %>
<h1 class="text-center">This is your report page</h1>
<form method="post">
<% for(let i=0; i<websites.length; i++){ let website = websites[i]; %>
<fieldset>
<label for="website<%=i%>" class="col-sm-2">Website <%=i+1%></label>
<input name="website<%=i%>" id="website<%=i%>" value="<%=website%>" type="text" />
</fieldset>
<% } %>
Generate report
</form>
<% include ./../partials/footer.ejs %>
The router, that's supposed to fire up after the on click.
router.get('/reports', function(req, res, next) {
if (req.user.isPremium == false) {
// Free user - Single report
var builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
let website = req.body.website0;
console.log(website);
}
});
How it works: The controller finds the account, grabs an array from inside of it, sends it to the view. The view prints it out and allows to make some changes to the values.
And now is where the problems begin. I need to gather the new values into an array and send it to the next router, which will then use them to call a bunch of APIs and print out the data. How do I gather the array and pass it to the controller? Also, should I use GET or POST?
With your logic, the name attribute will have the following form: name=website[0...n]. With that in mind, we can filter out the keys to gather all the website[n] into an array you seek:
const example = {
website0: 'example',
website1: 'example',
website2: 'example',
website3: 'example',
shouldBeIgnored: 'ignoreMe',
ignore: 'shouldIgnore'
}
const websites = Object.keys(example).filter(key => key.startsWith('website'))
console.log(websites)
So you're controller can be:
router.get('/reports', (req, res, next) => {
if (!req.user.isPremium) {
// Free user - Single report
const builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
const websites = Object.keys(req.body).filter(key => key.startsWith('website'));
console.log(websites);
}
});
I have troubles with data context;
Here is my code (unfortunatly, meteorpad is broken)
router.js(I use iron:router)
Router.configure({
layoutTemplate: 'layout'
});
Router.route('home',{
path: '/',
action: function(){
this.redirect('sections', {page: 0});
}
});
Router.route('sections', {
path: '/sections/:page',
data: function(){
var data = {};
data.params = {};
data.params.page = this.params.page?this.params.page:0;
return data;
}
});
template.html
<template name="layout">
{{>yield}}
</template>
<template name="sections">
Page: {{params.page}}
<br>
Page 0
Page 1
Page 2
<br>
<button>what page?</button>
</template>
template.js
Template.sections.onRendered(function(){
let scope = this;
$("button").on("click", function(){
alert("page: " + scope.data.params.page);
});
});
When I click button, button-handler has the scope, which had the template, when rendered, but not actual in this moment;
thanks to #user3374348
method Blaze.getData(scope.view) returns actual data context.
template.js
Template.sections.onRendered(function(){
let scope = this;
$("button").on("click", function(){
alert("page: " + Blaze.getData(scope.view).params.page);
});
});
I have no idea what I'm doing wrong, but I cannot get typeahead working in my MVC 5 application. I installed everything via NuGet and my view includes #Scripts.Render("~/bundles/typeahead"), which is rendering properly when viewing the source of the view. So the issue isn't that the dependencies are missing.
I am not seeing any drop down appear when I start typing, and using Fiddler I do not see any calls being made out to the remote that I setup that pulls the data.
Here's the line in my view that typeahead is being attached:
#Html.TextBoxFor(m => m.MainInfo.CompanyName,
new { #class = "form-control typeahead", id = "comp-name", autocomplete="off" })
Here's the portion of my script that configures typeahead and bloodhound:
$(document).ready(function() {
var clients = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: "/info/client?like=%QUERY",
wildcard: '%QUERY',
filter: function (clients) {
return $.map(clients, function (client) {
return {
value: client.Name,
clientId: client.Identifier
};
});
}
}
});
clients.initialize();
$('#comp-name').typeahead(null,
{
display: 'value',
minLength: 1,
source: clients.ttAdapter(),
templates: {
empty: "Looks like a new client...",
suggestion: Handlebars.compile("<p><b>{{value}}</b> - {{clientId}}</p>")
}
});
});
Is there something that I've configured wrong in my javascript? I've used a few tutorials as well as their own documentation, but I cannot figure out what I'm doing wrong here. It almost feels like it's not properly initialized, but there are no errors being thrown.
NOTE: Just as an FYI I'm using Bootstrap 3 as well in case that changes anything.
EDIT: Here's my #section Scripts:
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/typeahead")
<script src="#Url.Content("~/Scripts/handlebars.min.js")"></script>
<script src="#Url.Content("~/Scripts/ProjectSetupFormScripts.js")"></script> <-- this is where typeahead is set up
This did the trick for me:
JS
#section Scripts {
<script type="text/javascript">
$(function () {
SetupTipeahead();
});
function SetupTipeahead() {
var engine = new Bloodhound({
remote: {
url: '/Employees/AllEmployees',
ajax: {
type: 'GET'
}
},
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.FullName);
},
queryTokenizer: Bloodhound.tokenizers.whitespace
});
engine.initialize();
$('#FullName').typeahead(null, {
displayKey: 'FullName',
source: engine.ttAdapter(),
templates: {
empty: [
'<div class="empty-message">',
'No match',
'</div>'
].join('\n'),
suggestion: function (data) {
return '<p class="">' + data.FullName + '</p><p class="">' + data.ManNumber + '</p>';
}
}
});
}
</script>
EmployeesController has the following JsonResult
public JsonResult AllEmployees()
{
return Json(db.Employees.ToList(),JsonRequestBehavior.AllowGet);
}
Hello try to wrap your script in #section scripts {} this will place the script at the bottom just before the </body> tag and make sure you are not calling the function before your bundles load.
#section scripts {
<script>
$(document).ready(function() {
var clients = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: "/info/client?like=%QUERY",
wildcard: '%QUERY',
filter: function (clients) {
return $.map(clients, function (client) {
return {
value: client.Name,
clientId: client.Identifier
};
});
}
}
});
clients.initialize();
$('#comp-name').typeahead(null,
{
display: 'value',
minLength: 1,
source: clients.ttAdapter(),
templates: {
empty: "Looks like a new client...",
suggestion: Handlebars.compile("<p><b>{{value}}</b> - {{clientId}}</p>")
}
});
});
</script>
}
I am new to backbone, express, and mongodb.
I am trying to pass a query string to search a mongodb collection by field.
I am doing something wrong. If I comment out the "fetch" from my router, the page is found.
If I try to fetch, then I get a page not found error.
I've tried to isolate where it's breaking, but the backbone architecture is still confusing to me. Thanks in advance. (I'm betting it's a syntax issue in my mongodb call)
kristin
Here is my code.
this URL should return a collection where "type" = 3.
localhost:8888/#content/3
model/models.js:
window.Content = Backbone.Model.extend({
urlRoot: "/content",
idAttribute: "_id"
});
window.ContentCollection = Backbone.Collection.extend({
model: Content,
url: "/content"
});
views/content.js
window.ContentListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
//return this;
this.$el.append('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ContentView({model: model}).render().el);
}, this);
return this;
} });
window.ContentView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
views/main.js
var AppRouter = Backbone.Router.extend({
routes: { "content/:type" : "contentType" },
contentType: function(type) {
var contentList = new ContentCollection({type : type});
contentList.fetch({success: function(){
$("#content").empty().append(new ContentListView({collection: contentList}).el);
}});
this.headerView.selectMenuItem('build-menu');
},
utils.loadTemplate([
'ContentView'
], function() {
app = new AppRouter();
Backbone.history.start(); });
contentView.html
name (<% tag won't print here)
routes/modules.js
exports.findContentByType = function(req, res) {
var type = req.params.type;
db.collection('content', function(err, collection) {
collection.find({'type': type.toString()}).toArray(function(err, items) {
res.send(items);
});
});
};
server.js
app.get('/content/:type', module.findContentByType);
I can see a couple of problems here:
this.headerView.selectMenuItem('build-menu'); (in the router) implies you've defined headerView in the router object, but it's not defined.
Similarly, this.template inside ContentView is not defined
When I remove the line in #1, and and define a dummy template in ContentView:
template: _.template("<div> Test: <%= version %> </div>"),
Then the view at least renders -- see here. (This is with dummy data -- I can't confirm that your server is returning valid/expected JSON.)
Is there a way to register helper functions to EJS templates, so that they can be called from any EJS template? So, it should work something like this.
app.js
ejs.helpers.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%= sayHi('Bob') %>
Yes, in Express 3 you can add helpers to app.locals. Ex:
app.locals.somevar = "hello world";
app.locals.someHelper = function(name) {
return ("hello " + name);
}
These would be accessible inside your views like this:
<% somevar %>
<% someHelper('world') %>
Note: Express 2.5 did helpers differently.
I have another solution to this, and I think it has some advantages:
Don't polute your code exporting filters.
Access any method without the need to export them all.
Better ejs usage (no | pipes).
On your controller:
exports.index = function(req, res) {
// send your function to ejs
res.render('index', { sayHi: sayHi });
}
function sayHi(name) {
return 'Hello ' + name;
};
Now you can use sayHi function inside your ejs:
<html>
<h1><%= sayHi('Nice Monkey!') %></h1>
</html>
You can use this method to send modules to ejs, for example, you could send 'moment' module to format or parse dates.
Here's an example filter...I'm not familiar with helpers.
var ejs = require('ejs');
ejs.filters.pluralize = function(num, str){
return num == 1 ? str : str+'s';
};
<%=: items.length | pluralize:'Item' %>
Will produce "Item" if it's 1, or if 0 or > 1, produces "Items"
app.js
ejs.filters.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%=: 'Bob' | sayHi %>
I am using:
In helpers/helper.js
var func = {
sayhi: function(name) {
return "Hello " + name;
},
foo: function(date) {
//do somethings
}
};
module.exports = func;
In router:
router.get('/', function(req, res, next) {
res.render('home/index', {
helper: require('../helpers/helper'),
title: 'Express'
});
});
In template:
<%= helper.sayhi("Dung Vu") %>
goodluck