I have a form page that takes some inputs, stores the data in mongo and returns the data to the view underneath the form. However, when a user submits new data on the form, the jade view crashes and displays the following error:
TypeError: /Users/rhysedwards/Downloads/insightful/food/views/index.jade:29
27| label
28| input.btn.btn-default(name='submit', type='submit', onsubmit="this.submit(); this.reset(); return false;")
> 29| each Entry, i in entries
30| div.title= Entry.title
31| div.url= Entry.url
32| div.selectedDate= Entry.selectedDate
Cannot read property 'length' of undefined
The expected behaviour is that when a user submits new data through the form, it displays underneath the form. Why does Entry become undefined onsubmit but the data still displays prior to a submit?
Router
router.get('/', function(req, res, next){
Entry.find({}, function (err, entries) {
res.render('index', {
"entries": entries
});
});
});
Jade
block content
.container
h1 London Plans
form(method='post' action='post', class='plans', id='plans')
.form-group
label Do you have a link?
input.form-control(name='search', id='search' type='url', required='required')
h2#title
.form-group
label What looks cool?
input.form-control(name='title', type='text', required='required' class='title')
.form-group
label When is it?
label
input(type='checkbox' name='week' value='week')
span This week
label
input(type='checkbox' name='month' value='month')
span This Month
label
input(type='checkbox' id='date')
span Date
label
input(type='textbox' class='datepicker' name='date' value='')
label
input.btn.btn-default(name='submit', type='submit', onsubmit="this.submit(); this.reset(); return false;")
each Entry, i in entries
div.title= Entry.title
div.url= Entry.url
div.selectedDate= Entry.selectedDate
Post function
router.post('/post', function(req, res, next) {
var url = req.body.search;
var title = req.body.title;
var week = req.body.week;
var month = req.body.month;
var date = req.body.date;
console.log(url + ' ' + title + ' ' + week + ' ' + month + ' ' + date);
//FIND WHICH DATE WAS SELECTED BY USER AND ASSIGN THAT TO selectedDate
if (typeof week != 'undefined' ){
var selectedDate = 'week';
} else if (typeof month != 'undefined') {
var selectedDate = 'month';
} else {
var selectedDate = date;
};
//CREATE NEW OBJECT
var data = new Entry ({
url: url,
title: title,
selectedDate: selectedDate
})
//STORE NEW OBJECT TO THE DB
Entry.createEntry(data, function(err, entry){
if (err) throw err;
console.log(entry);
})
//RENDER THE HOMEPAGE TO CLEAR THE FORM
res.render('index');
return false;
});
The reason why entries is undefined upon resubmission is because in your router.post handler you're not supplying the Jade template with a value for that variable.
Here is the code in your router.post handler which causes this:
res.render('index'); // telling the page to render without variables
Now, compare this to the successful code in the router.get handler:
res.render('index', {
"entries": entries // here is where you assign variables for Jade
});
As you can see, in the router.get handler you're supplying the Jade template engine with a value for the "entries" variable with entries, which is a value returned by the call you make to mongodb.
It might be worth having a look at the express docs for res.render() for more information. Here's the excerpt in question which sheds some light on the situtation:
res.render(view [, locals] [, callback])
Renders a view and sends the rendered HTML string to the client.
Optional parameters:
locals, an object whose properties define local variables for the
view.
From the definition of the locals argument, you can see where you went wrong! Happy coding :)
Related
So I am trying to build a online appointment hospital booking website using express and mongodb.
Based on the date selected by the user, the time slots that are available should be displayed from the database dynamically without redirecting.
I tried to get data nad using ajax request but I am not able to display data.
To be honest I don't like this method. If anybody could suggest a better method I would be very happy to adpot it.
booking_appointment.js
let date_selected = document.getElementById("date-selected")
document.getElementById("myCalendar").addEventListener("click", function(e) {
e.preventDefault();
axios.post('/time_slots', {text: date_selected.value}).then(function (response) {
console.log("Success");
}).catch(function() {
console.log("Please try again later.");
})
})
app.js
app.get("/booking",function(req,res){
res.render("book");
});
app.post("/booking",function(req,res){
//req.body.text will give the selected date value.Based on this value time slots should be displayed from DB.
console.log(req.body.text);
});
app.post("/time_slots",function(req,res){
let date=req.body.text;
items=Slots.find_by(date: date).then((slots,err)=>{
res.render("book",{slots:slots});
}).catch(err=> req.flash('error',"Something went wrong!Try again!"))
});
app.post("/booking",function(req,res){
console.log("hurray");
res.render("home");
});
book.ejs
<form action="/booking" method="post" >
<Assume there is a calendar here from which we will get date id='myCalendar'>
<p><%= (typeof slots != "undefined" ? slots : "okay i need to figure it out") %></p>
</form>
Can u please tell me how to populate time slots dynamically, thanks in advance.
app.js
app.post("/time_slots",function(req,res){
let date=req.body.text;
items=User.find().then((slots,err)=>{
let data=JSON.stringify(slots);
return res.json({slots: data})
}).catch(err=> req.flash('error',"Something went wrong!Try again!"))
});
booking_appointment.js
let Pname = document.getElementById("Pname")
document.getElementById("myCalendar").addEventListener("click", function(e) {
//wwe dont the default action to performed after submitting the form. we want to send an asnychoronous request to server
e.preventDefault();
alert(e);
axios.post('/time_slots', {text: Pname.value}).then(function (response) {
// Create the HTML for a new item
var $section = $('<div class="col-md-4"></div>');
(JSON.parse(response.data.slots)).forEach(function(slot) {
$section.append(`<h1>${slot.name}</h1>`);
});
$('#myCalendar').html($section);
}).catch(function(e) {
console.log(e);
console.log("Please try again later.");
})
})
book.ejs
<form action="/booking" method="post" >
<Assume there is a calendar here from which we will get date id='myCalendar'>
</form>
<div id="myCalendar" class="vanilla-calendar"></div>
I'm figuring out what's the best way to update a current list of results from an API call, with a new list of results from an API call.
I'm making API request to news API and loading them into the index page when it first loads:
app.get("/", function (req, res) {
request("https://newsapi.org/v2/top-headlines?q=" + initialQ + "&category=sports&pageSize=10&page=" + page + "&sortBy=relevance&apiKey=" + apiKey, function (error, response, body) {
if (!error && response.statusCode == 200) {
let data = JSON.parse(body);
totalResults = data.totalResults;
console.log(totalResults)
let articles = scripts.articlesArr(data);
let filteredArticles = scripts.filteredArr(articles);
res.render("index", { filtered: filteredArticles });
} else {
res.redirect("/");
console.log(response.body);
}
});
});
Then the user will toggle two buttons to get more results, or go back a page:
app.post("/", function (req, res) {
let inputValue = req.body.page;
let pages = Math.ceil(totalResults / 10)
page = scripts.iteratePages(inputValue, page, pages);
request("https://newsapi.org/v2/top-headlines?q=" + initialQ + "&category=sports&pageSize=10&page=" + page + "&sortBy=relevance&apiKey=" + apiKey, function (error, response, body) {
if (!error && response.statusCode == 200) {
let data = JSON.parse(body);
let articles = scripts.articlesArr(data);
let filteredArticles = scripts.filteredArr(articles);
res.render("index", { filtered: filteredArticles });
} else {
res.redirect("/");
console.log(response.body);
}
});
});
I'm aware of Socket io, but I was wondering if there are other means or methods of achieving this? From what I understand, I can update frontend content via the front end - but with my current set up I'd much prefer to update from the back end
EJS code:
<div id="container">
<% for(var i=0; i < filtered.length; i++) { %>
<ul>
<li><%= filtered[i].title %></li>
<li><%= filtered[i].date %></li>
<li><img src="<%= filtered[i].image%>" /></li>
<li><%=filtered[i].description%></li>
<li><%= filtered[i].link %></li>
</ul>
<% } %>
</div>
<form action="/" method="POST">
<ul>
<li>
<button type="submit" name="page" value="next">Get more results</button>
<button type="submit" name="page" value="prev">Go back a page</button>
</li>
</ul>
</form>
For bi-directional communication we can use WebSockets (with a library like Socket.IO), for uni-directional server-to-client we can use EventSource, and for uni-directional client-to-server we use good ol' HTTP, through fetch or XMLHttpRequest in the browser API (this is referred to as AJAX, though I think most devs just says "client calls the server" these days). For 99% of use cases what we want is client-to-server over HTTP. If I understand correctly then you want stuff to happen when the users pushes a button. That's a case of client-to-server.
User pushes button
Client calls our new API endpoint /articles with fetch to get more articles: const data = await fetch('localhost:8080/articles'); const articles = await data.json(). A simplified version of the code for /articles looks something like app.get('/articles', (req, res) => request("https://newsapi.org").then(articles => /* do stuff with articles here */res.send(result)). This end point returns json instead of html (which our / endpoint returns)
Our server calls newsapi. Newsapi anserrs our server. Our server answers the client.
Then we need some data binding/templating that ensures that the DOM is updated with the new articles. This is functionality that libs like React and Angular supply. But for learning purposes and to keep things simple you can do something like articles.forEach(a => {const el = document.createElement('li'); el.innerHtml = a; document.getElementById('articles').appendChild(el)}), assuming a tag <ul id="articles">... where articles are supposed to show up exists (you probably want to do something more complex with your articles, but you get the idea)
Page hasn't reloaded 🙌
Update: some code review :)
use template literals. "https://newsapi.org/v2/top-headlines?q=" + initialQ + "&category=sports&pageSize=10&page=" + page + "&sortBy=relevance&apiKey=" + apiKey -> https://newsapi.org/v2/top-headlines?q=${initialQ}&category=sports&pageSize=10&page=${page}&sortBy=relevance&apiKey=${apiKey}
Prefer const over let
Use new lines when you're lines get very long (many go by 80 columns as preferred max width)
It looks like you do one ul for each article and one li for each property on the article. ul is a list (unordered list) and li is a list item. So one ul should contain many li, and each li should contain one item (in this case an article). You can read more about semantics in web development here
In my code below I have created an array of items in my .JS file. I was then able to pass this array to the .Jade and use each value in the array as an item in a dropdown list. I now want to pass the user input of which item they will click in the dropdown back to the server side (.js) so that I can use the user input to find more data.
My problem is that I don't know how to send the .jade variables to the server side. I want to send the "this.selectedIndex"/selected "val" so I can use it as a variable in the javascript file.
.JS
router.get('/', function(req, res) {
var projectPathArray = [];
async function main() {
var projects = await _db.listProjects();
projects.forEach(async (item) => {
var pathy = item.path;
projectPathArray.push(pathy)
})
res.render('index', { title: 'Projects', projectPathArray:projectPathArray});
}
main();
.jade
extends layout
script(src="libs/jquery-1.11.3.min.js")
link(rel='stylesheet', href='/stylesheets/style.css')
block content
h1= title
p To start, please select a project
html
body
form#test-form(action='', method='get')
select#menu1(name='menu1', size=projectPathArray.length)
each val in projectPathArray
option=val
Without understanding exactly what you want this should at least get you closer to what you are asking for.
1) Add the route to handle the post where you can retrieve the values posted back in the form using req.body.
2) In your Pug/Jade template I indented the form elements so they are under the form, added a submit button, and changed the method of the form to post.
.JS
router.post('/', function(req, res) {
console.log(req.body);
res.redirect('/');
});
router.get('/', function(req, res) {
var projectPathArray = [];
async function main() {
var projects = await _db.listProjects();
projects.forEach(async (item) => {
var pathy = item.path;
projectPathArray.push(pathy)
})
res.render('index', { title: 'Projects', projectPathArray:projectPathArray});
});
main();
.jade
extends layout
script(src="libs/jquery-1.11.3.min.js")
link(rel='stylesheet', href='/stylesheets/style.css')
block content
h1= title
p To start, please select a project
html
body
form#test-form(action='', method='post')
^
select#menu1(name='menu1', size=projectPathArray.length)
each val in projectPathArray
option=val
button(type='submit') Submit
You will need to use some mechanism for communicating from the frontend back to the server. This includes, but is not limited to, websockets and/or AJAX.
I want a user to be able to type 2 inputs into a form, hit submit, and have the database send back documents with field values that match the values that were typed into the form.
If I hard code values for 'name' and 'code' variables on the last line, I get the right results and everything renders fine. So I think the problem has to do with how I'm using the variables / variable scope, or something of that nature.
more detailed description below...
I am using Meteor.
I have a form with 2 input fields, for example, product and brand
I want to send a query of the following form:
PriceList.find({'name': name, 'brandCode': code});
I have a template that renders based on the results of this query. This relies on publishing the results of the query:
if (Meteor.isServer) {
Meteor.publish('byProductAndBrand', function(){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
I'm trying to use Meteor.subscribe() to change dynamically based on the form inputs:
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
}
});
Meteor.subscribe('byProductAndBrand');
}
Here's the relevant code (duplicates what I wrote above, but may be less annoying to read...)
PriceList = new Meteor.Collection('PriceList');
product = 'dummyProduct';
brand = 'dummyBrandCode';
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
}
});
Meteor.subscribe('byProductAndBrand');
}
if (Meteor.isServer) {
Meteor.publish('PriceList', function(){
return PriceList.find();
});
Meteor.publish('byProductAndBrand', function(){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
Pass params to Meteor.subscription :
if(Meteor.isServer){
Meteor.publish('byProductAndBrand', function(product, brand){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
}
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
var instance = Template.instance();
if(instance.byProductAndBrandHandle != null){
instance.byProductAndBrandHandle.stop();
}
instance.byProductAndBrandHandle = Meteor.subscribe('byProductAndBrand', product ,brand);
}
});
}
It seems to me that you would be better off using dependencies instead of subscriptions. This makes sense to me in this circumstance because only the one client viewing the page is looking at this particular price list, you are not publishing this price list for multiple users as far as I can tell.
For ex:
In declarations, before if(Meteor.isClient) and if(Meteor.isServer):
var byProductAndBrandDep = new Tracker.Dependency;
Inside of priceList helper:
byProductAndBrandDep.depend();
In "submit form#addDataSelectionForm".events: function(event, templates) {
byProductAndBrandDep.changed();
Then, every time the form is submitted, every helper that has the byProductAndBrandDep.depend() function inside it automatically updates.
I currently have the following code in a JS file, this is reading the list of links generated which have team names. When the link is clicked, the relevant textboxes are filled with the correct info for each team.
I have now put in the drop down list as shown below, and would like to do the exact same thing but using the textbox, so when you select one of the options from the drop down list, it fetches the data for that team.
When using the links, I'm taking a.teamLink and listening for whenever one of them is clicked, can anyone think as to how I would do this for the drop down list items? Here is the generated code for the drop down list and the relevant code as well below.
Any help welcome!
Generated drop down list HTML example:
<div id="teamDropDownDiv">
<select id="teamList" style="width: 160px;">
option id="1362174068837" class="teamDropDown">Liverpool</option>
</select>
</div>
Generated link HTML example:
<a id="1362174068837" href="#" class="teamLink">Liverpool</a>
JavaScript to listen for each link:
Team.initTeamsLink = function(){
$("a.teamLink").live("click", function(e){
e.preventDefault();
var teamId = ($(this)[0]).getAttribute('id');
$.get('/show/team/'+teamId, function(response){
if(response.retStatus === 'success'){
var teamData = response.teamData;
$('#teamId').val(teamData.key);
$('#teamName').val(teamData.name);
} else if(response.retStatus === 'failure'){
}
});
});
};
Drop down list JADE template for HTML
div#teamDropDownDiv
-if(teamsList.length > 0){
select#teamList(style='width: 160px;')
option
-each team in teamsList
option.teamDropDown(id="#{team.key}") #{team.name}
-}else{
No teams till now..
-}
Link list JADE template for HTML
div#teamListDiv
-if(teamsList.length > 0){
-each team in teamsList
a.teamLink(id="#{team.key}", href="#") #{team.name}
br
-}else{
h3 No teams till now..
-}
Team.initTeamsDD = function(){
$("teamList").live("change", function(e){
e.preventDefault();
var teamId = $(this).val();
$.get('/show/team/'+teamId, function(response){
if(response.retStatus === 'success'){
var teamData = response.teamData;
$('#teamId').val(teamData.key);
$('#teamName').val(teamData.name);
} else if(response.retStatus === 'failure'){
}
});
});
};
Show team function
/**
* Get Meta data of a Team
* Send back all the details of a Team
*/
app.get('/show/team/:key', function(req, res){
util.log('Serving request for url [GET] ' + req.route.path);
Team.findByKey(req.params.key, function(err, teamData){
if(!err && teamData){
teamData = teamData;
res.json({
'retStatus' : 'success',
'teamData' : teamData
});
} else {
util.log('Error in fetching Team by key : ' + req.params.key);
res.json({
'retStatus' : 'failure',
'msg' : 'Error in fetching Team by key ' + req.params.key
});
}
});
});
Maybe this works:
The option element needs to have the team ID as value:
<div id="teamDropDownDiv">
<select id="teamList" style="width: 160px;">
<option id="1362174068837" class="teamDropDown" value="1362174068837">Liverpool</option>
</select>
</div>
So the template should be something like:
div#teamDropDownDiv
-if(teamsList.length > 0){
select#teamList(style='width: 160px;')
option
-each team in teamsList
option.teamDropDown(id="#{team.key}",value="#{team.key}") #{team.name}
-}else{
No teams till now..
-}
And add a listener to change event of the select, and get the selected team ID from the value attribute:
$("#teamList").change(function(e){
e.preventDefault();
var teamId = $(this).val()
...