Looking to change a Jade assigned variable with the results of an ajax post so that the page's Jade loop utilizes the new data (updating only the parts of the dom that relate to the loop and not rendering the page over).
route.js
router.post('/initial', function(req, res) {
res.render('/page', {data: data})
})
router.post('/refresh', function(req, res) {
res.send(newdata)
})
index.jade
block content
- var fluxdata = data
each item in fluxdata
span= item
div#button
client.js
$(document).on('click', '#button', function() {
$.post('/refresh', function(newdata) {
var fluxdata = newdata
})
}
I tried out partials, but wasn't sure I was on the right track. Looked around the internet and stackoverflow for a while and can't find a similar question about Jade assignments.
// Jade template
block content
div(class="content")
- var fluxdata = data
each item in fluxdata
span #{item.id} : #{item.username}
div
button(id="add") POST Data
after your template is rendered your html will look like this
// HTML rendered
<div class="content">
<span>1 : Yves</span>
<span>2 : Jason</span>
</div>
<div>
<button id="add">POST DATA</button>
</div>
// backend code
var users = [
{
username: "Yves",
id: 1
},
{
username: "Jason",
id: 2
}
]
router.get("/initial", function(request, responser) {
response.render("index", { data: users})
})
router.post("/refresh", function(request, responser) {
users.push({username: "Alex",id: 1})
response.json({ data: users})
})
// Your jquery code
$("#button").on('click', function(event){
event.preventDefault()
$.post('/refesh', function(data) {
$(".content").html("")
for(var user in data) {
var span = $("<span>")
span.text(user.id + ": " + user.username )
$(".content").append(span)
}
});
})
in your get "/initial" route handler, your are rendering the
res.render("/page", {data : data })
before the template name you musn't put the / and the template in witch you are trying to use data that at push to the view is index.jade
router.post('/initial', function(req, res) {
res.render('index', {data: data})
})
Related
I have the code below and have 2 separate issues, so please bear with me on this:
Issue 1 [fetch ?]:
The data displayed doesn't change when the JSON change. Sounds like it's a cache issue as I can't see any HTTP request beside the original one. How can I force the JSON file to be downloaded again each time?
Issue 2 [handlebars ?]: with $(document.body).append(html); in the loop, it keeps re-writing the instead of editing the values. How can I change this?
Here is the code:
javascript.js:
async function fetch_json() {
try {
var resp = await fetch('http://localhost:8000/data.json', {mode: 'cors'});
var jsonObj = await jsonify(resp);
return jsonObj;
} catch (error) {
// all errors will be captured here for anything in the try block
console.log('Request failed', error);
}
}
html page:
<script id="handlebars-demo" type="text/x-handlebars-template">
<div>
{{#each this}}
Name : {{name}} Value : {{value}} <br>
{{/each}}
</div>
</script>
<script type="text/javascript">
var test_data = [{ "name" : "john doe", "value" : "developer" },{ "name" : "bob boby", "value" : "developer2" }];
setInterval(function() {
test_data = fetch_json()
.then(function(result) {
html = templateScript(result);
//$(document.body).append(html);
})
}, 1000);
var template = document.getElementById('handlebars-demo').innerHTML;
Compile the template data into a function
var templateScript = Handlebars.compile(template);
var html = templateScript(test_data);
$(document.body).append(html);
</script>
any help would be the most appreciated, thank you!
You should create a DOM element to hold the HTML you are generating. I've created <div id="content"></div> in the example.
You can use $().html() to overwrite the HTML each time instead of appending.
$('#content') selects the DOM element with id=content and then overwrite the HTML inside .html(string) with string.
A common approch to cache busting is to attach a timestamp to the url as a url query param, which I have done by concatenating nocache='+new Date().getTime().
In normal use in production a unique identifier is usually generated per version for each resource after building.
// for demo purposes, overwrite value property with username property
jsonify = x => x.json().then(x => x.map(x => ({ ...x,
value: x.username
})));
async function fetch_json() {
try {
// append timestamp to prevent caching
var resp = await fetch('https://jsonplaceholder.typicode.com/users?nocache=' + new Date().getTime(), {
mode: 'cors'
});
var jsonObj = await jsonify(resp);
return jsonObj;
} catch (error) {
// all errors will be captured here for anything in the try block
console.log('Request failed', error);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.6/handlebars.js" integrity="sha256-ZafrO8ZXERYO794Tx1hPaAcdcXNZUNmXufXOSe0Hxj8=" crossorigin="anonymous"></script>
<div id="content"></div>
<script id="handlebars-demo" type="text/x-handlebars-template">
<div>
{{#each this}} Name : {{name}} Value : {{value}} <br> {{/each}}
</div>
</script>
<script type="text/javascript">
var test_data = [{
"name": "john doe",
"value": "developer"
}, {
"name": "bob boby",
"value": "developer2"
}];
setInterval(function() {
test_data = fetch_json()
.then(function(result) {
html = templateScript(result);
$('#content').html(html);
})
}, 2000);
var template = document.getElementById('handlebars-demo').innerHTML;
//Compile the template data into a function
var templateScript = Handlebars.compile(template);
var html = templateScript(test_data);
$('#content').html(html);
</script>
I've looked at all the threads that already exist on this topic and have not been able to come up with a solution for my case.
I have multiple forms rendering with the help of Handlebars like this:
<ul>
{{#each listRecords}}
<form id="form{{id}}" action="/expand-list-records-save/{{../listId}}/{{id}}" method="POST">
<div class="record-box">
<li>{{recordTitle}} by {{artistName}} ({{releaseYear}})
<br>
<div>
<label>Paste media embed code here:</label>
<textarea class="form-control form-control-lg" name="embeddedmedia" cols="30" rows="10">{{embeddedMedia}}</textarea>
</div>
<br>
<br>
</li>
</div>
</div>
</form>
{{/each}}
</ul>
<input id="submit" class="btn btn-secondary btn-block" type="submit" value="Submit embed code" >
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
for (var i=0; i < document.forms.length; i++) {
console.log(`submitting ${document.forms[i].id}`)
document.forms[i].submit();
}
})
})
</script>
my Node.js + Express.js route looks like this
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
// console.log(req)
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
// console.log(req.body)
result.update({
embeddedMedia: req.body.embeddedmedia
})
}).then(() => {
console.log("sending list to view")
sendListDataToView({ params: {id: req.params.listId} }, res, 'view-list')
})
})
I'm having a few problems. First of all, this logic only executes a POST request for the item that the very last form on the page is for. Why is it that the console.log works for every single instance in my loop when iterating through all the document forms? From what I know, I think I need to use AJAX here somehow to execute all the POST requests. And the second main thing that I don't think is giving me problems at this point, but will once I get the first issue solved, is that my route is not written to handle a batch of requests like I need it to.
UPDATE
Upon a recommendation in comments, I decided try and write an Ajax request to post all of the forms to a separate route which will handle it from there. How do I pass an array of forms to the data parameter? I get the Uncaught RangeError: Maximum call stack size exceeded error this way:
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$.ajax({
type: 'POST',
url: window.location.origin + $('h3')[0].innerText,
data: document.forms,
success: (data) => {
console.log(data)
}
})
})
})
After going through some other examples, I tried rewriting original submit script like this. And, in this case, it does not pick up the action attribute.
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
$('form').each(() => {
var that = $(this);
$.post(that.attr('action'), that.serialize())
})
})
})
So, I have finally come up with a solution, if anyone is interested. Perhaps not prettiest, but it works.
<script>
$(document).ready(() => {
$('#submit').click(function submitAllForms() {
var counter = 0;
var totalForms = $('form').length;
$('form').each((i, form) => {
const redirectIfDone = () => {
if (counter === totalForms) {
alert("all forms updated successfully")
window.location.replace(window.location.origin + '/list/' + $('h3')[0].innerText)
}
}
if (!($(form).find('textarea').val())) {
counter++
redirectIfDone();
return true;
} else {
$.ajax({
type: 'POST',
url: $(form).attr('action'),
data: $(form).serialize(),
success: (data) => {
counter++;
redirectIfDone();
}
})
}
})
})
})
</script>
Virtually no changes to the route. Overall, I'm still interested in seeing other possible solutions.
router.route('/expand-list-records-save/:listId/:recordId')
.post((req, res) => {
Record.findOne({
where: {
id: req.params.recordId
}
}).then(result => {
result.update({
embeddedMedia: req.body.embeddedmedia
})
res.end()
})
})
I'm using CollectionFS to allow image uploads. The image uploads need to belong to specific posts. I followed the steps from the documentation - Storing FS.File references in your objects - however, I'm having a hard time displaying the image of the associated post.
The post currently saves with a postImage that references an image._id - this part is working fine. However, I am unsure how to display the actual photo, as it will need to grab the photo from the images collection (the post collection just saves an ID to reference).
post-list.html
<template name="postList">
<tr data-id="{{ _id }}" class="{{ postType }}">
...
<td><textarea name="postContent" value="{{ postContent }}"></textarea> </td>
<td>{{ postImage._id }} </td> // This currently displays the correct image._id, but I would like to display the image,
<td><button class="delete tiny alert">Delete</button></td>
</tr>
</template>
post-list.js
Template.postList.helpers({
posts: function() {
var currentCalendar = this._id;
return Posts.find({calendarId: currentCalendar}, {sort: [["postDate","asc"]] });
}
});
post-form.js - This form creates a new Post and Image. The Image._id is saved to the Post.postImage.
Template.postForm.events({
// handle the form submission
'submit form': function(event) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = $('.myFileInput').get(0).files[0];
var fileObj = Images.insert(file);
var currentCalendar = this._id;
var newPost = {
...
calendarId: currentCalendar,
owner: Meteor.userId(),
postImage: fileObj,
};
// create the new poll
Posts.insert(newPost);
}
});
use reywood:publish-composite and dburles:collection-helpers so;
Collections || collections.js
Posts = new Mongo.Collection('posts');
Images = new FS.Collection("files", {
stores: [
// Store gridfs or fs
]
});
Posts.helpers({
images: function() {
return Images.find({ postId: this._id });
}
});
Template || template.html
<template name="postList">
{{# each posts }}
{{ title }}
{{# each images }}
<img src="{{ url }}">
{{/each}}
{{/each}}
</template>
Client || client.js
Template.postList.helpers({
posts: function() {
return Posts.find();
}
});
Template.postList.events({
// handle the form submission
'submit form': function(event, template) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = template.find('.myFileInput').files[0];
Posts.insert({
calendarId: this._id,
owner: Meteor.userId()
}, function(err, _id) {
var image = new FS.File(file);
file.postId = _id;
if (!err) {
Images.insert(image);
}
});
}
});
Router || router.js
Router.route('/', {
name: 'Index',
waitOn: function() {
return Meteor.subscribe('posts');
}
});
Server || publications.js
Meteor.publishComposite('posts', function() {
return {
find: function() {
return Posts.find({ });
},
children: [
{
find: function(post) {
return Images.find({ postId: post._id });
}
}
]
}
});
When using CollectionFS, on the client side you need to ensure that your subscriptions are correctly defined. This is the biggest stumbling block i've encountered with my developers in using CFS - understanding and mapping the subscription correctly.
First things first - you need to have a subscription that is going to hit Images collection. I'm not familiar with the latest CFS updates for their internal mapping but the following approach has usually worked for me.
Meteor.publish('post', function(_id) {
var post = Posts.findOne({_id: _id});
if(!post) return this.ready();
return [
Posts.find({_id: _id}),
Images.find({_id: post.postImage})
]
});
For displaying, there is a handy CFSUI package( https://github.com/CollectionFS/Meteor-cfs-ui ) that provides some nice front end handlers.
With the above mapping your subscription, you can then do something like
{{#with FS.GetFile "images" post.postImage}}
<img src="{{this.url store='thumbnails'}}" alt="">
{{/with}}
I am attempting to make a element in a meteor template editable via a update function. The data changes when it is inserted from a server side code in the fixture.js code. However I have no luck updating it via a editable form with some Template.name.events({}); code and, creating a collection, publishing and subscribing to it. The very last piece of code is the fixture.js file. So in some regard I can insert into the collection and update it, but I have no luck with the edit financialsEdit template. The router.js file I included only contains parts regarding the financials template. If needed I will post more.
Basically I can't update a collection value with a update function using $set and passing a key value pair.
UPDATE: I added the permissions.js file in the lib directory to show what ownsDocument returns.
Here is my code.
client Directory
client/editable/edit_financial.js
Template.financialsEdit.events({
'submit .financialsEdit': function(e) {
e.preventDefault();
var currentFinanceId = this._id;
var financialsProperties = {
issuedOutstanding: $('#issuedOutstanding').val()
}
Financials.update(currentFinanceId, {$set: financialsProperties}, function(error) {
if (error) {
alert(error.reason);
} else {
console.log(financialsProperties);
// Router.go('financials');
Router.go('financials');
}
});
}
});
client/editable/financials_helpers.js
Template.financials.helpers({
financials: function() {
return Financials.find();
},
ownFinancial: function() {
return this.userId === Meteor.userId();
}
});
client/editable/financials
<template name="financials">
<div id="finance">
{{#each financials}}
<h2>Issued Outstand : {{issuedOutstanding}}</h2>
{{/each}}
</div>
</template>
client/editable/financials_edit.html
<template name="financialsEdit">
<form class="main form financialsEdit">
<input id="issuedOutstanding" type="number" value="{{issuedOutstanding}}" placeholder="{{issuedOutstanding}}" class="form-control">
<input type="submit" value="Submit" class="submit"/>
</form>
</template>
lib Directory
lib/router.js
Router.route('/financials', function () {
this.render('financials');
});
Router.route('/financialsedit', {name: 'financialsEdit'});
lib/collections/financials.js
Financials = new Mongo.Collection('financials');
Financials.allow({
update: function(userId, financial) { return ownsDocument(userId, financial); },
remove: function(userId, financial) { return ownsDocument(userId, financial); },
});
Financials.deny({
update: function(userId, financial, fieldNames) {
// may only edit the following two fields:
return (_.without(fieldNames, 'issuedOutstanding').length > 0);
}
});
lib/permissions.js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
server/publications.js
Meteor.publish('financials', function() {
return Financials.find();
});
server/fixture.js
if (Financials.find().count() === 0) {
Financials.insert({
issuedOutstanding: '43253242'
});
}
This is a MEAN stack (MongoDB, Express, Angular, and Node), using Jade as the template engine.
This form is creating users perfectly:
extends layout
block content
div(ng-controller="Controller")
.row
.col-md-6
.panel.panel-default
.panel-heading
h3.panel-title User Form
.actions.pull-right
| <i class="fa fa-chevron-down"></i><i class="fa fa-times"></i>
.panel-body
form(role="form" novalidate class="user-form")
.form-group
label First Name
input(type="text" class="form-control" placeholder="Eddard" ng-model="user.firstname")
.form-group
label Last Name
input(type="text" class="form-control" placeholder="Stark" ng-model="user.lastname")
.form-group
label Email
input(type="email" class="form-control" placeholder="estark#winteriscoming.net" ng-model="user.email")
.form-group
label Password
input(type="password" class="form-control" placeholder="super-secret-password" ng-model="user.password")
button(ng-click="reset()" class="btn btn-secondary") Reset
|
button(ng-click="update(user)" class="btn btn-primary") Save
.col-md-6
pre
| form = {{ user | json }}
pre
| master = {{ master | json }}
script.
function Controller($scope, $http) {
$scope.master = {};
$scope.update = function(user) {
$http({
method: 'POST',
url: '/users/create',
data: user,
}).success(function(response) { $scope.master = angular.copy(response); }) ;
};
$scope.reset = function() {
$scope.user = angular.copy($scope.master);
};
$scope.reset();
}
But I would like to use the same form to edit users, as well as create them. I have the routes setup like this:
exports.create_user = function(req, res) {
res.render('user-form', {title: 'Create User'});
};
exports.user = function(req, res) {
User.findById(req.params.id, function(err, user) {
res.render('user-form', {title: 'Edit User', user: user});
});
};
How can I make the user passed back to the exports.user route bind to the user object being used by Angular?
You will want to build an api and send json back, because what you're doing is rendering the user-form form again. So try something like this:
User.findById(req.params.id, function(err, user) {
res.send(user);
});
If you want to maintain a single page app, it's more about creating an API versus individual jade templates.