Polymer 1.0 - How to use 'page' to route app and change URL - polymer-1.0

Working from the Polymer 1.0 Starter Kit, I'd like to set up a new route, but I need to fire it from a function in my app.js file rather than through routing.html
app._loadProject = function(e) {
// do stuff here
// after finished, route to our 'project' section in the app
app.route = 'project';
};
This works for the most part. The application is routed to the 'project' <section>. However, the URL does not update to reflect this, so in cases where the user reloads the page, they find themselves on a different 'section' than the one they were just on - not the friendliest scenario.
Is there a more proper way to route with 'page' that doesn't break browser navigation?

Do your thing in app.js:
app._loadProject = function(e) {
// do stuff here
// after finished, route to our 'project' section in the app
page.show('/project'); // same as page('/project')
};
Add a rule in routing.html:
page('/project', project);
...
function project() {
app.route = 'project';
}

Related

Routing in SPA with ASP.NET MVC 6 and AngularJS

I have a sample MVC6 single page app with one view in which I want to load 2 Angular partials using ngRoute. You can have a look at it at GitHub
There are 3 URLs in the app:
localhost - Index.cshtml
localhost/games - Index.cshtml with Angular's gamelist.html partial
localhost/games/2 - Index.cshtml with Angular's game.html partial
The routes config is the following:
MVC:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
routes.MapRoute("gamelist", "games", new { controller = "Home", action = "Index"});
routes.MapRoute("gameWithId", "games/2", new { controller = "Home", action = "Index" });
});
Angular:
myApp.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/games', {
templateUrl: 'partials/gameslist.html',
controller: 'GameController',
controllerAs: 'ctrl'
})
.when('/games/:gameId', {
templateUrl: 'partials/game.html',
controller: 'GameController',
controllerAs: 'ctrl'
});
$locationProvider.html5Mode(true);
}]);
It all works perfectly fine as long as I start the app from the home page '/' and then navigate to the partials using the links on the page. The problem is that the URL #3 (localhost/games/2) does not work if I start the app from it, by typing it in the address bar. The URL #2 (/games/) does work.
The reason why #3 does not work is that MVC removes '/games' part from the URL and what Angular gets is just '/2'. If you run the sample app, you will see that '$location.path = /2'. Of course Angular cannot map using that path and no partial is rendered. So my question is - how to make MVC return the full path to the client so that Angular can map it?
You can get it to work with HTML5 mode, you just need to ensure that every request maps back to your Index.cshtml view. At that point the AngularJS framework loads, client-side routing kicks in and evaluates the request URI and loads the appropriate controller and view.
We've done this with multiple Angular apps inside MVC with different .cshtml pages, though we use attribute routing with the wildcard character, e.g.
[Route("{*anything}")]
public ActionResult Index()
{
return View("Index");
}
The wildcard operator (*) tells the routing engine that the rest of the URI should be matched to the anything parameter.
I haven't had chance to get to grips with MVC6 yet but I think you can do something like this with the "new" version of attribute routing?
[HttpGet("{*anything:regex(^(.*)?$)}"]
public ActionResult Index()
{
return View("Index");
}
To make link #3 work from the browser's address bar, I turned off "html5Mode" in Angular and made links #-based.
kudos to this blog
I think it is a better solution.
His solution is rewriting the request that doesn't fit to any route and doesn't have any extension to the landing page of angular.
Here is the code.
public class Startup
{
public void Configure(IApplicationBuilder app, IApplicationEnvironment environment)
{
// Route all unknown requests to app root
app.Use(async (context, next) =>
{
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/app/index.html"; // Put your Angular root page here
await next();
}
});
// Serve wwwroot as root
app.UseFileServer();
// Serve /node_modules as a separate root (for packages that use other npm modules client side)
app.UseFileServer(new FileServerOptions()
{
// Set root of file server
FileProvider = new PhysicalFileProvider(Path.Combine(environment.ApplicationBasePath, "node_modules")),
// Only react to requests that match this path
RequestPath = "/node_modules",
// Don't expose file system
EnableDirectoryBrowsing = false
});
}
}

Marionette/backbone app, href in a tag doesn't go to defined route

Here is my A tag in index.html.
<div class="menu-item">Login in
If I click it, it should go to '/login' route. But URL correctly changed to localhost:3333/#login in browser address input bar, but the page content shows no change, still in landing page.
Here is my code for starting Backbone history:
new Router();
Backbone.history.start({pushState: true, root: '/'});
Here is my code for router:
var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;
var _ = require('lodash');
var Marionette = require('backbone.marionette');
var OuterLayout = require('../layout/outerLayout/outerLayout');
var ol = new OuterLayout();
var AppRouter = Backbone.Marionette.AppRouter.extend({
routes : {
'': 'index',
'signup' : 'signup',
'login' : 'login'
},
index : function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
// outerLayout.footer.show();
},
signup : function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
var ContentSignup = require('../layout/outerLayout/view/contentSignup/contentSignup');
ol.content.show(new ContentSignup());
},
login: function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
var ContentLogin = require('../layout/outerLayout/view/contentLogin/contentLogin');
ol.content.show(new ContentLogin());
}
});
module.exports = AppRouter;
The result is that URL changed in the browser address input field, but the page content doesn't change. Then if I hit CMD + R to refresh the page, then the content will change, correctly reflecting the route.
Also the go back button on browser doesn't work, url changes, but the content doesn't change. I think I forget to call sth in my code to "refresh" the browser?
oh, I am using httpster to start a mini http server for this front-end development.
Have you tried this:
new AppRouter();
instead of this
new Router();
Unless you actually want to hit the server (which would just be /login and you'd deal with it on the server side). You should take the pub sub approach.
So in your view you would say:
triggers
"click .menu-item": "loginClicked"
Then in your controller you can listen to that event (if it's a composite view's childview that you're in you may have to prefix this with childview:):
#listenTo loginView, "login:button:clicked", (args) ->
App.vent.trigger "login:clicked"
Then in the router
API =
login: ->
new LoginsApp.Show.Controller
App.vent.on "login:clicked", ->
App.navigate "/login"
API.login()
So you end up navigating/hitting the same action that you would by going through the router, but you don't have to rely on the routes.
If you don't want to go that route I imagine the problem is that you need to say Backbone.history.navigate({trigger: true}) to get it to actually trigger the route in the approuter.
The best approach I've found is the approuter is there when the user clicks refresh or navigates directly to the page. But everything else should be handled in app with the pub sub approach. It gives you the most control that way.
Remove the slash (/) and use only "#route" on your hrefs, to avoid the browser from fetching the default document served at "/" from the backend.
By the way, watch your use of require, you should require the constructors at the top so the requirejs optimizer can fill in the dependencies on build time.
Something like:
//this before the component definition
var MyView = require("views/myview"),
AppLayout = require("views/layout");
//... later on your view/app/model definition
function foo(){
var view = new MyView();
}
I also think having a list of required stuff at the top of any file helps understanding it later on. ;)

Backbone.history is not updating in IE 9. Back button broken

In our app, we actually have two Backbone SPA applications. The first one is for login, registration and other features for unauthenticated users. The URL for this would be something like http://www.example.com/registration#signin. Once you login, you are redirected to our main Backbone app at http://www.example.com/ui#home.
In my main UI app, I am using Backbone.history without pushState. The App file looks something like:
define(function (require) {
var App = new Marionette.Application();
App.addInitializer(function (options) {
...
});
...
App.on('initialize:after', function () {
$(function(){
if (Backbone.history) {
Backbone.history.start({ root: '/ui' });
}
});
$.log("**WebApp**: Marionette app started.");
});
return App;
});
Of course, everything works flawlessly in any browser except IE 9 (and maybe 10, I need to check). In IE 9, all the routing works fine. Clicking links such as http://www.example.com/ui#anotherpage works. However, when the user clicks the Back button in their browser, they are not sent back to the last route fired. Instead, they are sent to http://www.example.com/registration#signin, which is the last page served by Node, our web server. As I click through links, I can see that history.length and Backbone.history.history.length are not updating.
All routes are fired from links/URL's. I'm not using router.navigate() within the code. Here are examples of our Router:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.AppRouter.extend({
appRoutes: {
"": "showHome",
"home": "showHome",
"foo": "showFoo"
}
});
});
And Controller:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.Controller.extend({
showHome: function () {
require(['webapp','modules/home'], function (WebApp) {
WebApp.module("Home").start();
WebApp.module("Home").controller.showModule();
});
},
showFoo: function () {
require(['webapp', 'modules/foo'], function (WebApp) {
WebApp.module("Foo").start();
WebApp.module("Foo").controller.showModule();
});
}
});
});
UPDATE:
On further research, it turns out the problem is that older versions of IE don't record hash changes in their history. See - Change location.hash and then press Back button - IE behaves differently from other browsers. But I'm still not sure what the workaround for this would be. I'm guessing it would somehow involve manually handling hash change events with a plugin such as jQuery Hashchange and doing... something? Manually setting IE's history? Or crafting a custom history object and using it when we detect a Back button in IE?
I was having the same problem in one of our apps for IE.
Starting backbone history like below works.
Backbone.history.start({
pushState: true,
hashChange: false
});
Update: As mentioned By T Nguyen,
When you set pushState to true, hash URL's no longer trigger routes. Unless you add server-side support for all your Backbone routes, you need to add an event handler on the client side which captures appropriate links and calls .navigate() on the route

Trigger and backbone router with file://

I'm working with trigger and backbone, and am trying to programmatically navigate to a url. This is all happening using the file:// protocol, as everything in running inside trigger io only.
This manual navigate though doesn't trigger the function associated with the route.
My router looks like this
var BARouter = Backbone.Router.extend({
routes: {
"users/sign_in": "userSignin",
"users/sign_up": "userSignup",
"": "catchAll"
},
userSignin: function(){
},
userSignup: function(){
forge.logging.info("in user signup----");
},
catchAll: function(){
}
});
var app_router = new BARouter();
BA.router = app_router;
Backbone.history.start({pushState: true});
and I'm manually navigating
BA.router.navigate(navigate_to("users/sign_up"), {trigger:true});
The navigate_to method just returns the full url in the form "file://users/sign_up".
But nothing is logged to the console, and the execution flows normally. Am I missing something here ?
Using pushState with file urls probably doesn't make sense, I'm also not sure why you need the navigate_to function.
Try setting pushState to false and navigate using the string of the route, i.e.:
BA.router.navigate("users/sign_up", {trigger:true});

Backbone Router not working using boilerplate

Ok, I think this is something simple, however I am being to stupid to see it. Here is my code in backbone using the backbone boilerplate method
require([
"app",
// Libs
"jquery",
"backbone",
// Modules
"modules/example"
],
function(app, $, Backbone, Example) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"": "index",
"item" : 'item'
},
index: function()
{
console.info('Index Function');
var tutorial = new Example.Views.Tutorial();
// Attach the tutorial to the DOM
tutorial.$el.appendTo("#main");
// Render the tutorial.
tutorial.render();
},
item: function()
{
console.info('Item View');
}
});
// Treat the jQuery ready function as the entry point to the application.
// Inside this function, kick-off all initialization, everything up to this
// point should be definitions.
$(function() {
// Define your master router on the application namespace and trigger all
// navigation from this instance.
app.router = new Router();
// Trigger the initial route and enable HTML5 History API support
Backbone.history.start({ pushState: true, root: '/reel' });
});
// All navigation that is relative should be passed through the navigate
// method, to be processed by the router. If the link has a data-bypass
// attribute, bypass the delegation completely.
$(document).on("click", "a:not([data-bypass])", function(evt) {
// Get the anchor href and protcol
var href = $(this).attr("href");
var protocol = this.protocol + "//";
// Ensure the protocol is not part of URL, meaning its relative.
if (href && href.slice(0, protocol.length) !== protocol &&
href.indexOf("javascript:") !== 0) {
// Stop the default event to ensure the link will not cause a page
// refresh.
evt.preventDefault();
// `Backbone.history.navigate` is sufficient for all Routers and will
// trigger the correct events. The Router's internal `navigate` method
// calls this anyways.
Backbone.history.navigate(href, true);
}
});
});
I am running this of a MAMP server and when i type Localhost:8888/reel , I get the example index page that comes with boilerplate. However when I type Localhost:8888/reel/item or Localhost:8888/reel/#item I either get, page can not be found or directed back to my index page.
My question is what am i doing wrong. Do I need to use htaccess? This doesnt seem right. Is there a way using backbone to sort this. Sorry if this is really simple, just cant get my head around it.
the problem may lie with the pushState flag.
With that on the request goes all the way to the server and it sees the full url and responds to it with whatever it would do ...
does it work if you have a
$(function (){
setTimeout(navMe, 2000);
});
function navMe() {
backbone.navigate("item");
}
that way 2 seconds after load it will navigate to item view and you know that its because of the request going to the server and not to backbone.

Resources