Hey Everyone i have a router that basically pulls in a JSON file and outputs various instances of the array. When i console.log the title in my for loop it loops through and outputs and each instance is outputted, works great. When i put my res.render inside of the for loop and pass it the same variable being looped in the console.log it will only output the first instance. Does anyone know why this is? and is there a way to loop through the res.render to output all instances in the JSON same as the console.log.
Thank you for your time,
router.get('/fuelTypeFilter', function(req, res, next) {
var url = "Example.JSON"
request({
url: url,
json: true
}, function (error, response, obj) {
if (!error && response.statusCode === 200) {
// console.log(obj) // Print the json response
for(key in obj.categories){
var img = obj.categories[key];
var title = obj.categories[key].product_category_title;
console.log(title);
// console.log(obj.categories[key]);
}
res.render('fuelTypeFilter', { title: 'Fuel Type', item: title });
} //end of if
}); // end of request
});
What res.render does is basically serve up a plain HTML file with some template inserted, so with data like the object you pass into the render call. Therefore, res.render acts like a return statement, as in when your code reaches that mark, it signals to the router that we need to render this page (present in the browser to user), therefore we must be done with our router handling logic.
As to whether it's possible to 'loop through' render calls, the answer is no since that would be implying to just, even if succeeded, present however many pages you want instantaneously one after the other, so in that perspective, you wouldn't really want to keep making render calls as your view in the browser would effectively keep refreshing moment after moment.
One way to do this with render is just package your array-like data into the object that you pass into res.render and then, assuming you are using some sort of templating engine, do something analogous to a 'for loop' there, looping and printing the elements as, say HTML divs.
Hope this is a bit helpful.
Related
I am writing a private plugin for nodebb (open forum software). In the nodebb's webserver.js file there is a line that seems to be hogging all incoming json data.
app.use(bodyParser.json(jsonOpts));
I am trying to convert all incoming json data for one of my end-points into raw data. However the challenge is I cannot remove or modify the line above.
The following code works ONLY if I temporarily remove the line above.
var rawBodySaver = function (req, res, buf, encoding) {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
app.use(bodyParser.json({ verify: rawBodySaver }));
However as soon as I put the app.use(bodyParser.json(jsonOpts)); middleware back into the webserver.js file it stops working. So it seems like body-parser only processes the first parser that matches the incoming data type and then skips all the rest?
How can I get around that? I could not find any information in their official documentation.
Any help is greatly appreciated.
** Update **
The problem I am trying to solve is to correctly handle an incoming stripe webhook event. In the official stripe documentation they suggested I do the following:
// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}),
(request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig,
endpointSecret);
} catch (err) {
return response.status(400).send(Webhook Error:
${err.message});
}
Both methods, the original at the top of this post and the official stripe recommended way, construct the stripe event correctly but only if I remove the middleware in webserver. So my understanding now is that you cannot have multiple middleware to handle the same incoming data. I don't have much wiggle room when it comes to the first middleware except for being able to modify the argument (jsonOpts) that is being passed to it and comes from a .json file. I tried adding a verify field but I couldn't figure out how to add a function as its value. I hope this makes sense and sorry for not stating what problem I am trying to solve initially.
The only solution I can find without modifying the NodeBB code is to insert your middleware in a convenient hook (that will be later than you want) and then hack into the layer list in the app router to move that middleware earlier in the app layer list to get it in front of the things you want to be in front of.
This is a hack so if Express changes their internal implementation at some future time, then this could break. But, if they ever changed this part of the implementation, it would likely only be in a major revision (as in Express 4 ==> Express 5) and you could just adapt the code to fit the new scheme or perhaps NodeBB will have given you an appropriate hook by then.
The basic concept is as follows:
Get the router you need to modify. It appears it's the app router you want for NodeBB.
Insert your middleware/route as you normally would to allow Express to do all the normal setup for your middleware/route and insert it in the internal Layer list in the app router.
Then, reach into the list, take it off the end of the list (where it was just added) and insert it earlier in the list.
Figure out where to put it earlier in the list. You probably don't want it at the very start of the list because that would put it after some helpful system middleware that makes things like query parameter parsing work. So, the code looks for the first middleware that has a name we don't recognize from the built-in names we know and insert it right after that.
Here's the code for a function to insert your middleware.
function getAppRouter(app) {
// History:
// Express 4.x throws when accessing app.router and the router is on app._router
// But, the router is lazy initialized with app.lazyrouter()
// Express 5.x again supports app.router
// And, it handles the lazy construction of the router for you
let router;
try {
router = app.router; // Works for Express 5.x, Express 4.x will throw when accessing
} catch(e) {}
if (!router) {
// Express 4.x
if (typeof app.lazyrouter === "function") {
// make sure router has been created
app.lazyrouter();
}
router = app._router;
}
if (!router) {
throw new Error("Couldn't find app router");
}
return router;
}
// insert a method on the app router near the front of the list
function insertAppMethod(app, method, path, fn) {
let router = getAppRouter(app);
let stack = router.stack;
// allow function to be called with no path
// as insertAppMethod(app, metod, fn);
if (typeof path === "function") {
fn = path;
path = null;
}
// add the handler to the end of the list
if (path) {
app[method](path, fn);
} else {
app[method](fn);
}
// now remove it from the stack
let layerObj = stack.pop();
// now insert it near the front of the stack,
// but after a couple pre-built middleware's installed by Express itself
let skips = new Set(["query", "expressInit"]);
for (let i = 0; i < stack.length; i++) {
if (!skips.has(stack[i].name)) {
// insert it here before this item
stack.splice(i, 0, layerObj);
break;
}
}
}
You would then use this to insert your method like this from any NodeBB hook that provides you the app object sometime during startup. It will create your /webhook route handler and then insert it earlier in the layer list (before the other body-parser middleware).
let rawMiddleware = bodyParser.raw({type: 'application/json'});
insertAppMethod(app, 'post', '/webhook', (request, response, next) => {
rawMiddleware(request, response, (err) => {
if (err) {
next(err);
return;
}
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
// you need to either call next() or send a response here
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
});
});
The bodyParser.json() middleware does the following:
Check the response type of an incoming request to see if it is application/json.
If it is that type, then read the body from the incoming stream to get all the data from the stream.
When it has all the data from the stream, parse it as JSON and put the result into req.body so follow-on request handlers can access the already-read and already-parsed data there.
Because it reads the data from the stream, there is no longer any more data in the stream. Unless it saves the raw data somewhere (I haven't looked to see if it does), then the original RAW data is gone - it's been read from the stream already. This is why you can't have multiple different middleware all trying to process the same request body. Whichever one goes first reads the data from the incoming stream and then the original data is no longer there in the stream.
To help you find a solution, we need to know what end-problem you're really trying to solve? You will not be able to have two middlewares both looking for the same content-type and both reading the request body. You could replace bodyParser.json() that does both what it does now and does something else for your purpose in the same middleware, but not in separate middleware.
I am trying to return an array of data inside a JSON object that is return from a URL, I can see the data that is being returned using console.log.
However when trying to catch the return array in a variable for example:
var arr = list();
console.log(arr.length);
The length being output by this code is "0" despite the fact that the data returned has content (so the length is greater than zero). How can I use the data?
list: function() {
var grades = [];
$.getJSON(
"https://api.mongolab.com/api/1/databases", function(data) {
console.log(data);
grades [0] = data[0].name;
console.log(grades.length);
});
return grades;
},
The issue you are facing is easy to get snagged on if you aren't used to the concept of asynchronous calls! Never fear, you'll get there.
What's happening is that when you call the AJAX, your code continues to process even though the request has not completed. This is because AJAX requests could take a long time (usually a few seconds) and if the browser had to sit and wait, the user would be staring in angsuish at a frozen screen.
So how do you use the result of your AJAX call?
Take a closer look at the getJSON documentation and you will see a few hints. Asynchronous functions like getJSON can be handled in two ways: Promises or Callbacks. They serve a very similar purpose because they are just two different ways to let you specify what to do once your AJAX is finished.
Callbacks let you pass in a function to getJSON. Your function will get called once the AJAX is finished. You're actually already using a callback in the example code you wrote, it's just that your callback is being defined inside of your list() method so it isn't very useful.
Promises let you pass in a function to the Promise returned by getJSON, which will get called once the AJAX is finished.
Since you are doing all this inside of a method, you have to decide which one you're going to support. You can either have your method take in callbacks (and pass them along) or you can have your method return the promise returned by getJSON. I suggest you do both!
Check it out:
var list = function(success) {
// Pass in the callback that was passed into this function. getJSON will call it when the data arrives.
var promise = $.getJSON("https://api.mongolab.com/api/1/databases", success)
// by returning the promise that getJSON provides, we allow the caller to specify the promise handlers
return promise;
}
// Using callbacks
list(function(grades) {
console.log(grades);
});
// Using promises
list()
.success(function(grades) {
console.log(grades);
});
I have a controller that uploads and processes a file. Afterwards, I wish to render the processing result in a modal div. I wanted to know what the best way is to get the results from the controller to the modal div on the gsp. I thought about a template but I didn't know how to specify what the target div for the template should be because this template wouldn't be rendered by a button click where a target for template render is set as an attribute, it would be done on a timed basis (i.e. when the file is done uploading). The other way is to send JSON back from the controller but I don't know how to intercept this JSON at the right time because I still don't quite understand the timings of the information flow between the GSP and the Controller. I know how to send the JSON but how to alert the GSP that "hey, some JSON is now ready for your modal that's about to go up." Here is some pseoducode of basically what I am trying to get done.
Controller:
upload() {
// process file and store results in three integers
// int1 = result1
// int2 = result2
// int3 = result3
// send the three numbers to the gsp
}
Now what is the best way to get these three numbers to the GSP so that they are displayed on a modal dialog which is about to go up like this:
<div id="fileUploadResultsModal">
Results:
${int1}, ${int2}, ${int3}
</div>
Here is the JS associated with my ajax upload function:
$("#chseFile").upload("${createLink(controller: 'customer', action: 'upload',)}",
{dataTypegrp: parseInt(getCheckedValue(document.getElementsByName('dataTypegrp'))),
fileTypegrp: parseInt(getCheckedValue(document.getElementsByName('fileTypegrp')))},
function(success) {
$("#cancel1").trigger("click");
setTimeout(function(){
$("#summary").trigger("click");
}, 250);
displaySuccess(data);
},
function(prog, value) {
console.log(value);
$("#prog").val(value);
if (value == 100) {
$("#prog").hide();
$("#progressbar").html("Uploading and processing. Please wait...");
}
});
but right now JS complains that 'data' is not defined. 'data' is meant to be the JSON coming back from the controller.
Thanks
you can render them as JSON:
render( [ int1:111, int2:222, int3:333 ] as JSON )
or as a HTML-string
render "<div id=\"fileUploadResultsModal\">Results:${int1}, ${int2}, ${int3}</div>"
or use a template
render template:'/yourController/templateName', model:[ int1:111, int2:222, int3:333 ]
or a TagLib
render g.yourResultTag( int1:111, int2:222, int3:333 )
For this tiny bit of information, the performance is not of concern. It's rather a matter of taste, or what is more appropriate for your client.
If the later is JSON-biased, use JSON-rendering. If it has a mix of JSON and HTML, use others.
inside controller at the enf of controller action you can use
render [data:['name':'firstname','surname':'secondName'] as JSON]
this will render the data to GSP
Consider a REST URL like /api/users/findByCriteria which receives POSTed JSON that contains details of the criteria, and outputs a list of Users.
How would one call this with Restangular so that its results are similar to Restangulars getList()?
Restangular.all('users').post("findByCriteria", crit)... might work, but I don't know how to have Restangular recognize that the result will be a list of Users
Restangular.all('users').getListFromPOST("findByCriteria", crit)... would be nice to be able to do, but it doesn't exist.
Doing a GET instead of a POST isn't an option, because the criteria is complex.
Well,
I experience same problem and I workaround it with plain function, which return a plain array of objects. but it will remove all Restangular helper functions. So, you cant use it.
Code snippet:
Restangular.one('client').post('list',JSON.stringify({
offset: offset,
length: length
})).then(
function(data) {
$scope.clients = data.plain();
},
function(data) {
//error handling
}
);
You can get a POST to return a properly restangularized collection by setting a custom handler for OnElemRestangularized in a config block. This handler is called after the object has been Restangularized. isCollection is passed in to show if the obect was treated as a collection or single element. In the code below, if the object is an array, but was not treated as collection, it is restangularized again, as a collection. This adds all the restangular handlers to each element in the array.
let onElemR = (changedElem, isCollection, route, Restangular: restangular.IService) => {
if (Array.isArray(changedElem) && !isCollection ) {
return Restangular.restangularizeCollection(null, changedElem, changedElem.route);
}
return changedElem;
};
RestangularProvider.setOnElemRestangularized(onElemR);
I have a very simple app that loads a JSON array using $http.get inside of a controller. It assigns the result data to scope and the HTML repeats through the results to display a list. It is a very simple angularjs example.
function ViewListCtrl($scope, $http) {
$scope.shoppinglist = [];
$scope.loadList = function () {
var httpRequest = $http({
method: 'POST',
url: 'viewList_service.asp',
data: ''
}).success(function (data, status) {
$scope.shoppinglist = data;
});
};
$scope.loadList();
}
While testing on a slow server I realized there was a 3 second blocking delay while rending the repeat region. Debugging revealed to me that my controller does not attempt to get the data until the page is loaded. My page takes 3 seconds to load. Then I must wait another 3 seconds for the json data to load.
I want to load data as soon as possible so it is ready when my controller is ready. Simply put, I want to pre-load the data so it loads in parallel to my module.
I've searched all over and the closest thing I found was "resolve", but I am not using routes. This is a very simple list and no routes or templates.
How can I load the JSON as soon as the page starts to render so it is ready when the controller is ready and there is no blocking... and then get that data into scope?
You can use the module.run method. Which is executed after the config stage is completed. To make use of this you need to create a service factory that does the actual query and caches the result
module("myapp",[]).factory('listService', function($q,$http) {
var listData;
var defer = $q.defer();
return {
loadList: function() {
if(listData) {
defer.resolve(listData);
}
else {
var httpRequest = $http({
method: 'POST',
url: 'viewList_service.asp',
data: ''
})
.success(function(data) {
listData=data;
defer.resolve(listData);
}
}
return defer.promise;
}
}
});
In your controller you still use the factory to get the data with a promise then wrapper.
listService.loadList().then(function(data) {
$scope.shoppinglist=data;
});
This way you can make the async call even before any controller related code executes.
You can load data by writing code (something like Chandermani's listService) in separate file but without using angular. you can use jquery ajax to load your data.
Then write a service in angular to read that data and pass it to your controller.
It sounds like your latency comes from a slow network + server, and not a large amount of data.
So, you could render a tag into your page, so that the data would be sent along with the page HTML response. The downside there is that you're hard-coding your data into your page. This tag would basically pre-seed the $http cache with your data.
var listJson = {...json data here, rendered server-side...};
mod.run(function($cacheFactory) {
var cache = $cacheFactory('yourListCacheId');
cache.put('list-cache-id', listJson);
// store "cache" somewhere you can retrieve it, such as a service or value.
});
Then either use the $http cache property, or wrap $http in a custom service which checks the cache.
Of course, the root of the problem is that your server takes 3 seconds per request, when you normally want that at least in the sub-second range.