Polymer and page.js wildcards in routes - javascript

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. :)

Related

Express with Handlebars will not load data into variables

I have a NodeJS/Express app that is using Handlebars for templates.
All of the templates and partials load fine except where I am returning data from an Express API.
The data is returned and I can see it in the Chrome debugger.
In this template, I am defining the HTML in a script and compiling it in JS.
Here is the template HTML:
<script id="search-result-template" type="text/x-handlebars">
<div>String</div>
{{#each patient}}
<div>
{{cp_first_name}}
</div>
{{!-- {{> searchresultpartial}} --}}
{{/each}}
</script>
The actual page is quite a bit more structured but I've narrowed it down to this for debugging.
Here is the code that compiles the template:
let patientSearchButton = document.getElementById('patient-search-execute');
patientSearchButton.addEventListener("click", async function (e) {
e.preventDefault();
let patientSearchFirstname = document.getElementById('patient-search-firstname')
let cp_first_name = patientSearchFirstname.value;
let url = baseURL + 'patientsearchquery/' + cp_first_name;
const response = await fetch(url, {
method: 'get',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
var data = response.json();
let patientList = await data;
patient = patientList;
if (response.status === 200) {
let patientSearchResultTemplate = document.querySelector("#search-result-template").innerHTML;
let patientSearchResultTemplateFunction = Handlebars.compile(patientSearchResultTemplate);
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction(patient);
let contentPartial = document.getElementById('patient-search-table');
contentPartial.innerHTML = patientSearchResultTemplateObject;
if (Handlebars.Utils.isArray(patient)) {
console.log("Array");
} else {
console.log("Not");
}
console.log(patient);
} else {
alert("HTTP-Error: " + response.status);
}
});
I can see the data from the API and I'm verifying that Handlebars sees it as an Array.
It seems to break when it enters the #each helper.
I've tried to shift the context with ../ and I've tried every variation I can think of the coax the data into the template.
I was concerned that being in an event handler tied to a button click, that the "this" context was breaking. I moved the code outside of the event handler and "this" seemed to be correct in Chrome but the behavior did not change.
Here is the array data in Chrome:
When paused on a breakpoint in Chrome, I can see that the patient data is present when being passed to the template.
I know it's something dumb but my head hurts from banging it against the wall.
This has happened on two different templates. Granted, they were similar, but I've tried numerous variations and it still isn't loading.
Thanks for any help you might offer.
Does anybody see anything obvious?
Addendum:
I changed the code to pass the property and I can see it in Chrome now.
It still doesn't show up in Handlebars.
this.patients shows the data in the console. Why won't it render the variable?
The {{#each patient}} in your template anticipates that the data that you pass to your template function has a property named patient and that the value of this property is iterable - like an Array.
However, it appears that you are simply passing an Array directly to your template function, and that Array does not have a patient property, and so the #each loop never executes.
One way to make this work would be to pass an Object to your template function and to assign to that Object a key, patient, with its value being your Array of patients.
This would require changing:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction(patient);
to:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction({ patient: patient });
Or use the shorthand:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction({ patient });
Note: As patient is an Array of Patient Objects, if I were on your team, I would urge you to add an "s" to its name to make it plural.
Ultimately, the problem was that the data was seen Handlebars as a Sequelizer Object. It was unable to access the prototype due to a security update in Handlebars 4.6.0.
Rather than using "allowInsecurePrototypeAccess" which is a widely suggested fix but one that seems like unsound advice, I made a deep copy of the data by using JSON.stringify() and JSON.parse() and Handlebars was able to access it.
It was a context error, just not the one I was expecting.

Pass parameter to Express controller from button click

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.

Get values from link that brought my to the current page

I know - the first thing you're gonna say is "Use the referrer!"
But that'd be the wrong answer, and I'll explain why.
Assume you're on a page, say "Employees", that has a search. The search fields for name yield a table of employees upon submitting. In that table, the Employee Name is a clickable object, in this case a hyperlink. That link goes to an Employee Details page. On that page, there isn't really anything, because that page actually loads up a bunch of Angular data from a subpage.
So, the problem:
On Employees, you click on a link:
http://localhost/EmployeeData#/details/cc30e1f8-b21c-4384-9710-e927686c925c
That link takes you to the EmployeeData controller and index view. The index view loads up details.html, which is an angular happiness full of data. It works great.
But back in the Index view, there's a tiny bit of code that tells the details where to show up, and has a breadcrumb. So in the Index ViewResult, (EmployeeData/Index.aspx), I'd like to be able to pull the GUID from the link that got you there... But when I use the referrer, it just tells me "You came from the Employee Search".
public ViewResult Index()
{
var vm = _vmp.GetViewModel<EmployeeDataViewModel>();
vm.Breadcrumb = CreateBreadCrumbDisplay();
return View(vm);
}
public string CreateBreadCrumbDisplay()
{
var referrer = (Request.UrlReferrer != null) ? Request.UrlReferrer.AbsoluteUri : string.Empty;
var curUrl = Request.ServerVariables["SCRIPT_NAME"];
//do lots of stuff
return theBreadCrumb;
}
In this instance, CreateBreadCrumbDisplay just returns http://localhost/Employee. And curUrl just has "EmployeeData", thanks to all the real data stuff happening in the angular page, and not actually in the Index view.
So, what I need to do is grab the full URL, with the ID, and pass that ID into my "CreateBreadCrumbDisplay" method.
Thoughts?
I think a possible solution would be to grab the URL params with $routeParams and pass it to your CreateBreadCrumbDisplay method from Angular.
If I understand your problem correctly reading up on $routeParams here should solve your problem.
Here is an example of how it should work:
First config your routes in Angular
myApp.config(function($routeProvider) {
$routeProvider.when('/link/:GUID', {
controller: 'SomeCtrl',
templateUrl: 'sometemplate.html'
});
});
Then in your controller
myApp.controller('SomeCtrl', function($scope, $routeParams) {
console.log($routeParams);
});
Link to it from somewhere
foo
And finally the console output:
{ GUID: "cc30e1f8-b21c-4384-9710-e927686c925c" }
As long as I understand , you can use $location service , $location.absUrl(); And you may succeed this in many other ways even passing your url to action method as a parameter while submitting .

PathJS elegant way of ignoring rest of the url

I am using path.js. I have a service like /foo/bar/baz and I use baz as parameter. So my router is like:
Path.map("#/foo/bar/:baz").to(function(){
//call the service
});
However sometimes I need a url like: /foo/bar/baz/ignore/whatever/etc/bla/bla. But /whatever/etc... part is not predictable as we may have more than one parameter.
So is there a possible way to get rest of url as a parameter after a certain level. I need something like:
Path.map("#/foo/bar/:baz(/*rest)").to(function(){
var params = this.params['rest'] ? this.params['rest'] : this.params['baz'];
});
in this case rest = ignore/whatever/etc/bla/bla

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