Pass parameter to Express controller from button click - javascript

Preface: I am writing a Web App using Node.js, Express, and MongoDB.
When a button is clicked I am trying to pass a parameter via URL from my index.ejs to the Express controller ItemController.js in order to dynamically create a filtered set of data.
The button is defined within index.ejs as follows:
<button type="button" class="btn btn-primary" value="/items/items" onclick="loadItems(value, 'Scripting Languages')">
Scripting Languages
</button>
In my external Javascripts file which houses miscellaneous functions, loadItems executes the following:
function loadItems(page, subcategory) {
window.history.replaceState({}, '', "?subcat=" + subcategory); //set URL Param
$('#mainContent').load(page); //load item listing page into grid within HTML GridLayout
}
After this, the router/controller handles the data filtering and page rendering:
Router (item.js)
...
// Get all items
router.get('/items', item.items);
...
Controller (ItemController.js)
...
// loads item listings
itemController.items = function(req, res) {
Item.find({}).exec(function (err, items) {
if (err) {
console.log("Error:", err);
}
else {
var URLSubcat = "Scripting Languages"; //this variable needs to change
var filtered = items.filter(function(el) {
return el.subcategory === URLSubcat;
});
res.render("../views/items/items", {items: filtered});
}
});
};
...
I have tried using req.query.subcat in order to try and grab the parameter from the URL, however it comes back as undefined. I have also tried the techniques in the links below to no avail. What should I be doing to accomplish this? Is there a better way to go about this?
Previously Tried
How to get GET (query string) variables in Express.js on Node.js?
How to access the GET parameters after "?" in Express?

$('#mainContent').load(page);
The URL you are requesting is stored in the page variable.
loadItems(page, subcategory)
… which is defined as an argument
onclick="loadItems(value, 'Scripting Languages')"
… which is passed from the onclick event handler
value="/items/items"
… which gets it value from that attribute.
The query string doesn't show up because the URL you are requesting does not include a query string!
The only query string you have is from here:
window.history.replaceState({}, '', "?subcat=" + subcategory);
… which modifies the URL in the browser addressbar and history without actually requesting it.

Related

Why is the Inertia.post method dumping form data into the query string?

I am using Inertia in a Laravel-React stack and have a page component that lets me write, edit, and delete blog posts and preview them as they would be styled on the site in real time.
I have a form that uses the State hook to update the value of its fields and display form data in a styled div on the right. I also have POST routes that direct to a controller which does some transforming and inserts/updates/deletes the site database with form data.
The top of the form, in React 17:
<div className="editor">
{props.id && <h3>Editing {props.title}</h3>}
<form onSubmit={(e) => {
e.preventDefault;
!props.id ? // are we editing an existing post?
Inertia.post('gutenberg.new') : // if no, then insert a new one
!delPost.bool ? // if yes, are we deleting it?
Inertia.post('gutenberg.update') : // if no, then update with this ID
Inertia.post('gutenberg.delete') // if yes, then delete with this ID
One of the controller's public methods, in this case, for the "edit" route. Sorry if there is too much information here. I am curious if it's forbidden to use this kind of logic in a controller when trying to do something like this, or if I am breaking something else inadvertently. In PHP using Laravel 9:
public function updatePost(Request $request) {
$postID = $request->input('id');
BlogPost::where('id', '=', $postID)->update([
'title' => $request->input('title'),
'slug' => $request->input('slug'),
'body' => $request->input('body'),
'image' => $request->input('image'),
'language' => $request->input('language'),
]);
/* Now, we pull the existing associative records for the post in
from the post tags table to compare them to the tags that were
sent to the backend. That way, I can edit the tags for each post
within the editor itself.
*/
$comparisonTags = PostTags::join('tags', 'tags.id', '=', 'post_tags.tag_id')
->where('post_tags.post_id', '=', $postID)
->get();
// Get the tags included in the request
$collectedTags = collect(json_decode($request->input('tags')));
foreach ($collectedTags as $index => $tag) {
// Add any totally new tags
Tags::upsert(['name' => $tag], ['id', 'name']);
/* "If the tags included in the update request contain something
that the post tags table doesn't for this post ID, then insert
them there." */
if (!$comparisonTags->contains('name', $tag)) {
$tagID = Tags::select('id')->where('name', '=', $tag)->get('id')->toArray();
$scalarTagID = strval($tagID[0]['id']);
PostTags::insert([
'post_id' => $postID,
'tag_id' => $scalarTagID,
]);
}
}
foreach ($comparisonTags as $id => $tag) {
/* "If the tags in the post tags table associated with the
updated post's ID contain something that the new tag list
does not, then remove those tags from the post tags table."
*/
if (!$collectedTags->contains($tag->name)) {
PostTags::where('tag_id', '=', $tag->id)->delete();
}
}
return Redirect::route('index');
// Inertia requests need Inertia responses
// country roads... take me home...
}
public function deletePost(Request $request) {
// pretty self-explanatory
$postID = $request->input('id');
BlogPost::where('id', '=', $postID)->delete();
PostTags::where('post_id', '=', $postID)->delete();
return Redirect::route('index');
}}
And the route in question:
Route::post('gutenberg/edit',
[Gutenberg::class, 'updatePost'])
->name('gutenberg.update');
The problem occurs when using the Inertia.post method to send form data to the controller for handling there. The issue does not show up when I use the normal HTML form-action-method approach, but I want to use Inertia's visit method so that I can use CSRF protection on the site.
Upon submitting the form, rather than redirect to the Inertia response that the controller returns for its POST method functions, the page just dumps all of the form data into the query string. Nothing happens on the backend, as no updates are made to the database, and the old form data is still visible on the page.
A screenshot of my browser after hitting "Submit." Note the query string, with a title attribute of "a very different title" from the originally exasperated ffs.
Could the controller be introducing some kind of unexpected JSON response, thus breaking the Inertia.post method? I stepped through the whole thing with Firefox's debugger, and the only thing I saw that looked out of the ordinary was a terrifying 16,000-character wide line that included that string about each Inertia request needing a valid Inertia response.
Any help would be appreciated.
I forgot the brackets on event.preventDefault().

Write to a existing js file using javascript

Suppose I have a file on server file1.js
Now my file1.js contains an code:
var arr = [ {'id':1, 'name':'one'},
{'id':2, 'name':'two'} ]; // array of two objects
There are two input boxes:
ID: <input id>
Name: <input name>
<button type="submit" action="xyz">
Now on clicking the submit button, I want to open file1.js and add {'id':<user_entered>, 'name':<user_entered>} i.e. an object to the existing array.
Is it doable? I don't want to involve any database here.
arr.push(..)
is the temporary solution i.e. If I open the web page on other PC or refresh the page, the source rendered here will not contain an array of 3 objects..isn't it????
If you are in a server-side environment you could first load the file using fs.readFile, parse the JSON into a normal JS array, manipulate the array however you need (i.e. append the extra object), and then write the serialized JSON back to the same path.
It would look something like this:
fs.readFile(path.join(__dirname, 'myFile.json'), 'utf-8', (err, data) => {
if (err) {
console.log('Error reading file');
}
const myArray = JSON.parse(data);
myArray.push({ new: 'object' });
fs.writeFile(path.join(__dirname, 'myFile.json'), JSON.stringify(myArray), e => {
if (e) {
console.log('Error writing file');
}
console.log('Success');
});
});
If you are only building for the web, however, this sounds like a job for localStorage. It would work very similarly:
const savedArray = localStorage.getItem('my_saved_array');
const myArray = savedArray ? JSON.parse(savedArray) : {};
myArray.push({ new: 'object' });
localStorage.setItem('my_saved_array', JSON.stringify(myArray));
It is possible for JSON.parse to throw and error so you may want to wrap this in a try-catch. This also gets around the issue of opening the page in a different tab as localStorage is persisted for each site in the same browser. If you were to open the site in chrome and then in safari, however, localStorage will not sync.
Hope this helps.

Polymer and page.js wildcards in routes

Am using the polymer starting kit and am trying to access routes to users that does not necessary have a link to it on the page.
routing.html:
page('/users/:name', function(data) {
app.route = 'user-info';
app.params = data.params;
});
On the page i have:
<a href$="{{baseUrl}}users/Addy">Addy</a><br>
<a href$="{{baseUrl}}users/Rob">Rob</a><br>
<a href$="{{baseUrl}}users/Chuck">Chuck</a><br>
<a href$="{{baseUrl}}users/Sam">Sam</a>
It is matching the 4 users with the specified urls correct, but i want the route to match any name even tho there is no link to the page.
It will work with the route '/users/*' that will match everything, but i do not want to use that if i dont have to.
That route you have there will achieve what you want. The way page.js uses the : character is as an identifier that what follows after this should be treated as a parameter. Lets say we have the following route:
page('/users/:name', function(data) {
app.route = 'user-info';
app.params = data.params;
});
With that we can match the following urls:
/users/Addy
/users/Chuck
/users/somebody
The :name part of the route can be whatever we want and it will be matched.
To know what the :name part is on your polymer code you need to get it from the function that we specified when we defined the route. The data parameter that is passed on the function contains that info.
On the route we have defined here, we can access the :name parameter with data.params.name inside the route definition.
page('/users/:name', function( data ){
//We have access to the :name parameter here
});
Best way to let the elements that want the data to know what it is. Is to set your :name parameter to a variable on your app object.
page('/users/:name', function( data ){
app.route = 'user-info';
app.params = data.params;
});
That code sets the whole parameter data to a variable on your app object. In this case we want to access the :name parameter. The way we go about it is to pass the app.params object to the element that needs that data and access the :name parameter with params.name, that assumes that you have named the element property in whic you use it params.
<custom-elem params="{{params}}></custom-elem>
Expecting that element to be child element of the <template is="dom-bind" id="app"> element.
With that in mind we can go to any route that matches the route so /users/anything and if you have set up your user page properly you will see the info for the user in this case info for anything user.
You can read more about routing with page.js here: Visionmedia Page.js
To solve the issue from coming from outside the current site use following code.
var users = function(data){
app.route = 'user-info';
app.params = data.params;
};
page('/users/:name', users);
page('//users/:name', users);
It isn't the most elegant thing but will solve the issue. It will cause ugly urls that have two slashes.
Better way to solve the second problem.
Don't us the hashbangs: true option set it false. Instead set the base url to this: page.base('/#'); That solves the problem and removes the annoiyng ! from the url. :)

Fetch Backbone Collection by Model IDs list

I have a REST API serving a few URLs:
/rest/messages
provides all messages. A message is a JSON/Backbone Model
{
title: 'foo',
body : 'bar'
}
To get a single message I have:
/rest/messages/:id
Is it possible to fetch a Backbone Collection using message IDs array? I don't want the whole message list, but just a few messages I specify by ID.
I could fetch Models one-by-one and fill up the Collection, but I'm wondering if Backbone has a cleaner way to do this.
Thanks
According to documentation, you can pass ajax options to the fetch call. So, you can pass ids as data attribute to the fetch call being done and based on it, return the respective models from the server.
For example (when doing fetch),
collection.fetch({
data : {
message_ids : [1, 3, 5] // array of the message ids you want to retrieve as models
}
})
This message_id array will be accessible as parameters (not sure of the name in your case) in the server code being executed at /rest/messages, from there you can return only specific models based on ids you receive as message_ids. The only thing you need is, client side must be aware of the ids of all the message models it needs.
You can use any data structure instead of array to send message_ids.
The url property of collection reference to the collection location on the server. When you use fetch, backbone uses that url.
The url property can be also a function that returns the url. So you can do something like that:
var ids = [1,2,3]
var messages = new MessegecCollection();
messages.url = function() {
return "/rest/messages/"+ids.join("-"); //results "/rest/messages/1-2-3"
}
messages.fetch();
You can also create a method in your collection that generated and set the url, or even fetchs a set of models.
Now all you have to do is to support this url: /rest/messages/1-2-3
Hope this helps!

How should I handle Asp.net MVC URLs in javascript calls?

I am attempting to write a javascript heavy portion of my Asp.net MVC Web App (this portion of the website is a RIA using Extjs). However, I have come up to a standstill at the correct way to handle URLs in the javascript.
For example, right now I have an Ajax call to the List action in the ObjectsController, which resides in the Reading area. The List action takes a parameter of documentId (int). As of right now, this maps to /Reading/Objects/List since I have no changed routing yet (the site is too young at the moment to finalize routes). Normally in a view, to put this URL in a string I would do #Html.Action("List", "Objects", new { area = "Reading", documentId = 3).
However, this doesn't work when dealing with javascript, since javascript isn't parsed by a viewengine.
To get around this, I have a very small view that returns javascript constants, such as URLs, that is loaded prior to my main application's js files. The issue is that I can't call Html.Action for this action because at constant creation time I (obviously) do not know what documentId the ajax calls are going to be, and if you exclude documentId from the Html.Action call an exception occurs. The documentId could change during the normal workflow of the application.
How do I handle this? I don't want to hardcode the URL to /Reading/Objects/List because if I change my routing for this (for a more user friendly json API), or this web app isn't hosted on the root of the domain, the URL will no longer be valid.
How does everyone else handle MVC URLs in their javascript calls?
Here's a safe technique that I've been using. Even if your route changes, your JavaScript will automatically conform to the new route:
<script>
var url = '#Url.Action("List", "Objects", new { area = "Reading", documentId = "_documentId_")';
var id = 100;
var finalUrl = url.replace('_documentId_', id);
</script>
"_documentId_" is essentially a dummy placeholder. Then inside my JavaScript, I replace "_documentId_" with the proper id value once I know what it is. This way, regardless of how your route is configured, your URL will conform.
Update: Dec 20
I just saw this interesting blog post. The author built a library that allows you to build routes inside of your JavaScript file with intellisense support in VisualStudio.
http://weblogs.asp.net/zowens/archive/2010/12/20/asp-net-mvc-javascript-routing.aspx
Personally I use unobtrusive javascript and avoid mixing markup with javascript. AJAX calls are normally triggered by clicking on some buttons or links:
#Html.ActionLink("click me", "List", "Objects",
new { area = "Reading", documentId = 3 }, new { id = "foo" })
and then in a separate js file I would attach and handle the onclick event (example with jquery):
$(function() {
$('#foo').click(function() {
$('#resultDiv').load(this.href);
return false;
});
});
As you can I didn't need to use any hardcoded URL in my javascript file. URLs should always be handled by the routing engine and generated with html helpers.
If it was a <form> instead of a link I would simply handle the onsubmit event (the same way) and use the form's action attribute to get the URL.
UPDATE:
After pointing out in the comments section that the documentId is known only at client-side you could do this:
#Html.ActionLink("click me", "List", "Objects",
new { area = "Reading" }, new { id = "foo" })
And then:
$(function() {
$('#foo').click(function() {
$('#resultDiv').load(this.href, { documentId: '123' });
return false;
});
});
Turns out, this was all solved by using Url.Action() instead of Html.Action(). Url.Action() is (so far) allowing me to generate URLS without all of the parameters. I am assuming that this only works when the route does not specify the parameters in the target URL itself.

Categories

Resources