erikringsmuth / app-router Goto Github PK
View Code? Open in Web Editor NEWRouter for Web Components
Home Page: https://erikringsmuth.github.io/app-router/
License: MIT License
Router for Web Components
Home Page: https://erikringsmuth.github.io/app-router/
License: MIT License
I'm writing a full-screen app (fullbleed) and relying on the flexbox/layout features of Polymer for my layout design.
When I moved to the app-router approach, I lost my screen measurements and have been working through the implications since. At this point, I'm liberally applying 'layout/flex' to my app and I seem to be getting somewhere, but I'm still losing my flexbox support when I cross the app-route/import boundary.
Before I go trundling down the path of debugging/modifying app-router itself, is this something others have played with in the past? Is there an established pattern I should be following? Unfortunately I'm simultanouesly new to Polymer, flexbox/layouts, and app-router at the same time so I'm not really certain what is 'supposed' to work and what needs custom coding.
Right now my pseduo-code is looking like:
<html>
<body fullbleed layout vertical unresolved>
<app-router flex layout vertical>
<app-route flex layout vertical> <!-- flex here isn't working as its dividing the screen real-estate amoung all my routes, I really mean 'flex but only on the active route' -->
<imported-page-template flex layout vertical> <!-- flex isn't working at all here, does not seem to cross the import boundary -->
...
Any thoughts for the lost and weary?
I have a <paper-tabs>
controlling a <core-animated-pages>
, and while it's easy to make the right page in the <core-animated-pages>
display on load, depending on the path in the browser, I can't figure out how to change page in the <core-animated-pages>
using the <paper-tabs>
(and get the pretty animation of the <core-animated-pages>
) while at the same time pushing the state to make bookmarking, sharing and reloading work as intended.
What is the best practice?
This is an extension of PR #43 from @sohjsolwin.
Example router.
<app-router core-animated-pages transitions="cross-fade-all">
<app-route path="/first" element="first-page"></app-route>
<app-route path="/second" element="second-page"></app-route>
<app-route path="/page/:pageNum" element="third-page"></app-route>
</app-router>
Paths /page/1
and /page/2
will both match the last route. The transition between these two pages won't be animated because setting coreAnimatedPages.selected = 3
doesn't change the page.
This may require a big re-think of the app-router
/ core-animated-pages
implementation...
Hi, is there any way how to get which route is currently active/selected? I would like it to know especially on the first load of the site
App-routes have an 'active' attribute when they are active. But that doesn't seem right to chceck which route has this attribute.
Hi, everything work in Chrome but when I export it to Cordova nothing seems to appear.
<body unresolved fullbleed>
<app-router>
<app-route path="/" import="/elements/kwikblood-fblogin.html"></app-route>
<app-route path="/panel" import="/elements/kwikblood-panel.html"></app-route>
</app-router>
<script src="scripts/app.js"></script>
</body>
This is basically what I've done
Great router. Been playing around with it and it all works as expected. One thing I wondered would be how compatible it is with animated pages?
In particular if I look at this example of animating sections of the page from chip to card using sections.
http://www.polymer-project.org/components/core-animated-pages/demos/music.html
Can you imagine how this might work with your router so that we could have for example.
/ (default)
/fragments
/past-prologue
as pages that we could use the back and forward button to navigate?
I think this is important as single page web apps and material design in a way blur the boundary for what is one page and what is the next but I think the user still wants the back button and a url link to work as expected.
Hope this is the right place to put this comment!
So I built a polymer app that uses app-router, and it works great on local host, but when I deployed the app with ftp-deploy, it replaced my website with a blank page. none of the pages that should be loaded through app-router appear. if you inspect the website you see etc, thats it, none of the content. Is there an important step that I am missing for getting these polymer apps ready for deployment? Any advice would be greatly appreciated! Thank you!
Add a before-data-binding
event to hook into the model before it is bound to the custom element or template.
<app-route ... on-before-data-binding="{{updateRouteModel}}"></app-route>
The event handler
updateRouteModel: function(event) {
// event.detail looks like this
{
path: '/new-path',
route: newRoute,
oldRoute: oldRoute,
model: {
// path variables and query parameters
// modify or add properties here before data binding happens
}
}
}
Then you could modify the model with any values.
I'm having dramas with path paramaters being automatically type casted.
In some instances, I'd prefer to know that I'm getting a string. If there was an option to disable the auto type casting, that'd be awesome.
I have my index.html, but i've put an element that wraps my application.
Then, inside my element, I've put app-router, so, it haven't been worked.
index.html
<link rel="import" href="elements.html"/>
...
<my-application></my-application>
elements.html
<link rel="import" href="my-application.html">`
<link rel="import" href="bower_components/app-router/app-router.html">`
my-application.html
<polymer-element name="my-application">
<template>
<app-router>
<app-route template path="*">
<template>
<script>alert('Welcome');</script>
</template>
</app-route>
<app-router>
<template>
<script>
...
</script>
<polymer-element>
Really digging app-router. On my current project I need to call measureHeaderHeight
on an element during core-animated-pages
's core-animated-pages-transition-prepare
event. I do this by having each element implement a willPrepare method that gets called by the application every time the transition-prepare
event fires.
Im curious how I could go about doing something similar with app-router. Looking through the docs, I didn't see a way to actually get at the element itself when a route is changing. I realize this is not an ideal use case (it would be awesome if the element could handle this itself) but at the moment, I'm not sure of any way for the element to know when it is about to be transitioned in.
adding routes dynamically works great unless you are currently at the route that is being added
It would be very useful to be able to pass attributes through to the imported component.
Syntactically, I envisage this could work in two ways. Using an attribute called 'title' as an example...
1 - Passing all unknown attributes on to the imported component:
<app-router>
<app-route path="/" import="components/home-page.html" title="Welcome"></app-route>
...
</app-router>
2 - Allowing the route to contain an instantiated component with the attribute added:
<link rel="import" href="components/home-page.html">
...
<app-router>
<app-route path="/">
<home-page title="Welcome"></home-page>
</app-route>
...
</app-router>
Nice component BTW!
In some browsers a hashchange
also triggers a popstate
. As far as I can tell there isn't a check to see if the browser will fire one or both. The temporary fix is to keep track of the previous state (URL) and not load the route if it's to the same previous state.
this.previousState = '';
this.stateChangeHandler = function() {
if (this.previousState !== window.location.href) {
this.previousState = window.location.href;
this.go();
}
}.bind(this);
window.addEventListener('popstate', this.stateChangeHandler, false);
window.addEventListener('hashchange', this.stateChangeHandler, false);
This needs a better check so that hashchange
and popstate
always call router.go()
.
It would be really awesome to have events for things like routeChangeStart and routeChangeEnd as well as the ability to defer route until a certain action has been resolved, much like AngularJS router
Two difficult things in client-side routing:
#
fragments (ex: URL /page#elementId
and anchor <a name="elementId"></a>
)I want to get these features working if possible. In the mean time you can do something equivalent to hash links with a scrollTo
query parameter like this.
http://example.com/page?scrollTo=elementId
<polymer-element name="demo-app">
<template>
<app-router id="router" on-activate-route-end="{{scrollTo}}">
<app-route path="/" import="/pages/home-page.html"></app-route>
<app-route path="/page" import="/pages/other-page.html"></app-route>
<app-route path="*" import="/pages/not-found-page.html"></app-route>
</app-router>
</template>
<script>
Polymer('demo-app', {
scrollTo: function(event) {
// look for a scrollTo query parameter
if (event.details.model.scrollTo) {
var el = document.querySelector('#' + event.details.model.scrollTo);
if (el && el.scrollIntoView) {
el.scrollIntoView(true);
}
}
}
});
</script>
</polymer-element>
You can find the documentation on scrollIntoView()
here https://developer.mozilla.org/en-US/docs/Web/API/Element.scrollIntoView.
This isn't supported by all browsers, but it's an example of a workaround. Tweak it to your needs and hopefully I can find a way to make the router scroll correctly when possible.
This is a more a proposition than an issue.
Using your tag, I've done a small app to try out polymer.
Doing so I sayed myself that i'd like to have a nice way to trigger a redirection without leaking out all the page in the app.
So i've used custom event to make my pages ask for redirection.
Each page will fire an event, which will eventually be catch by the app-router.
When the router get this event, he find the route that name with the name given with the event. And then redirect to the founded route.
Would you like to add this to your element ? If so i'l ll make a PR.
Want to see what i'm talking about ?
have a look at https://github.com/benzen/runner
Everything loads in the network console with nothing missed, but its gets a " Platform is not defined " from polymer.js
???
It'd be great to be able to match using regular expressions.
Example: match a pattern like '/word/number'
<app-route path="/^\/\w+\/\d+$/i" regex import="/page/cust-el.html"></app-route>
The issue is escaping the regular expression string in the HTML. Also how would it be called from JS?
route.setAttribute('regex', 'regex');
route.setAttribute('path', /^\/\w+\/\d+$/i);
When navigating between routes, app-router destroys the active view via removeRouteContent
(line308). While this is good for memory management, the user experience suffers as routes need to be rebuilt each time.
It would be great to have the capability to preserve views in the background to enable a snappier UI.
This could be accomplished through an attribute on app-router
or app-route
.
I am quite unsure whether this is a problem with Polymer or app-router, so this should be considered more of a help request than a bug filing.
I need to determine the value of an attribute set through data-binding when my element is ready. This works fine when I freshly load the page. However, when I visit the page through a link or router.go()
, the value will always be empty.
What could possibly cause this?
ex. If you have a route for /foo, and then go to /bar#/foo it'll load the /foo view, even though the /bar prefix is on it.
It would also be nice to have some way to disable hash urls entirely and only allow push state.
Currently you have to run tests from the browser.
https://github.com/erikringsmuth/app-router#build-test-and-debug-
Tests can't be run in Node.js test runners (Jasmine, Mocha) or PhantomJS test runners because none of them support custom elements.
The problem can be partially mitigated by moving "utility" functions to a different file. These are the functions that don't rely on DOM APIs.
parseUrlPath(url)
https://github.com/erikringsmuth/app-router/blob/master/src/app-router.js#L183
testRoute(routePath, urlPath, trailingSlashOption, isRegExp)
https://github.com/erikringsmuth/app-router/blob/master/src/app-router.js#L214
routeArguments(routePath, urlPath, url, isRegExp)
https://github.com/erikringsmuth/app-router/blob/master/src/app-router.js#L214
I don't want create globals so loading a <script src="...">
is out of the question. I'd like to move to ES6 modules and add a transpile step to gulp. This would allow some code to be tested with Jasmine or Mocha and run from gulp.
Hi @erikringsmuth,
This app-router is awesome. However I just want to ask if there's a way to hide the routes inside index.html. The routes are exposed when inspecting the code.
Thanks.
From https://github.com/erikringsmuth/app-router/blob/v2.3.0/src/app-router.js#L111:
AppRouter.go = function(path, options) {
if (this.getAttribute('mode') !== 'pushstate') {
// mode = auto or hash
path = '#' + path;
}
if (options && options.replace !== true) {
window.history.pushState(null, null, path);
} else {
window.history.replaceState(null, null, path);
}
stateChange(this);
};
Since that block checks for the existence of options
, if no options object is passed in it will default to the else block and use replace mode. Docs don't explicitly say what the default is but the example makes it seem like replace mode must be specified with the option, and that would make the most sense.
I can't figure out why an element or template you want to import should care about what layout it belongs to. I thought the point of custom elements is they would be re-usable. What I would expect is for a route to load an element or template and insert it into a tag.
E.g.
<!DOCTYPE html>
<html>
<head>
<title>Some Page</title>
<link rel="import" href="bower_components/app-router/app-router.html">
<link rel="import" href="bower_components/pushstate-anchor/pushstate-anchor.html">
...
<base href="/">
</head>
<body unresolved fullbleed>
<app-router mode="pushstate" trailingSlash="ignore">
<app-route path="/home" import="/pages/home.html" template></app-route>
<app-route path="/dashboard" import="/pages/dashboard.html" template></app-route>
<app-route path="/" import="/pages/home.html" template></app-route>
</app-router>
<core-scaffold id="scaffold">
<nav>
<core-toolbar tool flex>
...
</core-toolbar>
<core-menu selected="0">
<paper-item icon="label-outline" label="Home">
<a href="/home"></a>
</paper-item>
<paper-item icon="label-outline" label="Dashboard">
<a href="/dashboard"></a>
</paper-item>
</core-menu>
</nav>
<div fit>
<content>
<!-- templates and elements are loaded here -->
</content>
</div>
</core-scaffold>
</body>
</html>
Switch gh-pages to use the new core-style element.
I wondered why my ready event for my Polymer element in the inline template is called twice and I found out that this only happens when I have it inside of the inline template's root path (/):
<app-router id="router" mode="auto">
<app-route path="/">
<template>
<ivx-page-welcome fit></ivx-page-welcome>
</template>
</app-route>
If I have it inside of another route as "/" the Polymer ready event is fired only once.
It seems to me that the template content is re-inserted/renderer after the template was already rendered.
Or I'm doing it wrong?
Is there a way to make it possible to hit the back button without the page being "Not found". Additionally is it possible to use app router in a way that allows one to go directly to another page besides the home page. for example, I was able to get http://mararenzsmith.com working well, but if you go to http://mararenzsmith.com/photography you get an error. Navigating through the navigation drawer to the photography link works great but can't seem to get it to work directly. Thank you!
google code prettify randomly fails half way though a gh-page and prevents the rest of the page from loading.
I'm using app-router
to populate a sidebar navigation as well as the content of an app. It goes like this:
app-router
app-route path=/builder/*
.. showing menu for builderapp-route path=/runner/*
.. showing menu for runnerand inside some other nodes:
app-router
app-route path=/builder/home
.. show the contentapp-route path=/builder/list
.. show some listapp-route path=/runner/home
.. show the contentI poked at the code, and it seems the second app-router
isn't taken into account, because the first one alters the app-router global previousUrl
, and the second pass is short-circuited here https://github.com/erikringsmuth/app-router/blob/master/src/app-router.js#L145
I would really love app-router
to be able to handle multiple instances again. Can't we store the previousUrl on the node itself ?
thanks!
I think app-router could use regular expressions in order to match path variables. Regex with named captures would be great but Javascript doesn't support them natively (xregexp is a js library that support named captures). Even simple native capture groups (by index number) would be great.
An example:
<!-- url -->
http://www.example.com/demo/15
<!-- route, notice variables attribute -->
<!-- first capure [word (\w+)], second capture [number (\d+)] -->
<app-route path="/^\/(\w+)\/(\d+)$/i" regex variables="word number" import="/pages/regex-page.html"></app-route>
<!-- data binding -->
<regex-page word="demo" number="15"></order-page>
It's much more powerful than actual path variable data binding (using semicollon like :pathArg1).
Thank you for app-router.
refresch options passed in value to true when we call the method go. In a scenario with a global filter for example to traslate not refresh the page and global filter not work. This involves the following changes in the method go
Chages in AppRouter.go
AppRouter.go = function(path, options) {
if (this.getAttribute('mode') !== 'pushstate') {
// mode = auto or hash
path = '#' + path;
}
if (options && options.replace !== true) {
window.history.pushState(null, null, path);
} else {
window.history.replaceState(null, null, path);
}
stateChange(this,options && options.refresch);
};
Changes in stateChange
// Find the first that matches the current URL and change the active route
function stateChange(router,refresch) {
var url = utilities.parseUrl(window.location.href, router.getAttribute('mode'));
// don't load a new route if only the hash fragment changed
if (!refresch && url.path === previousUrl.path && url.search === previousUrl.search && url.isHashPath === previousUrl.isHashPath) {
scrollToHash(url.hash);
return;
}
previousUrl = url;
// fire a state-change event on the app-router and return early if the user called event.preventDefault()
var eventDetail = {
path: url.path
};
if (!fire('state-change', eventDetail, router)) {
return;
}
// find the first matching route
var route = router.firstElementChild;
while (route) {
if (route.tagName === 'APP-ROUTE' && utilities.testRoute(route.getAttribute('path'), url.path, router.getAttribute('trailingSlash'), route.hasAttribute('regex'))) {
activateRoute(router, route, url);
return;
}
route = route.nextSibling;
}
fire('not-found', eventDetail, router);
}
This allows call the method go as follows go ('/', {refresch: true}) and thus to make the cycle is complete page
it looks for the polymer.html down in app router bower folder.
http://127.0.0.1:49399/app-router/bower_components/polymer/polymer.html
Just a minor thing, it seems the element tag needs to be set for an imported component for vulcanizing to work even if the imported component has the same name as the file.
For example, I have a component in a file name home-page.html exported with a name of 'home-page'.
This code:
<app-route path="/" import="components/home-page.html"></app-route>
Results in polymer throwing an error when loaded after vulcanization:
Uncaught HierarchyRequestError: Failed to execute 'appendChild' on 'Node': Nodes of type 'HTML' may not be inserted inside nodes of type '#document'.
This code fixes it:
<app-route path="/" import="components/home-page.html" element="home-page"></app-route>
Cheers!
because everything is evaluated client side some things are different
in the data-binding-page.html page if i add an in page anchor it self evaluates incorrectly by the browser engine to : http://127.0.0.1:49399/pages/data-binding-page.html#anchor1
Here is the html in the page.
How to tell polymer to evaluate the path at runtime properly ???
Hello,
clicking on core-items in core-toolbar in my app does not trigger routing on mobile devices (URL does not get changed). Routing itself works (typing and surfing directly to /#/xyz).
I did not change any behaviour, just put some content into the sample app. Navigation in the sample https://erikringsmuth.github.io/app-router/#/ works, too!
Not sure if this is some kind of bug?
Tested on Chrome for G3, Nexus 9 and Safari on iPhone 6 and 4S.
You can test it on resume.shadysblog.com
Refactor parseUrl()
to return more information including the type of route (hash vs regular) and start and end offsets of the path.
This can be used to implement something like trailingSlash="redirectToMatching"
where this route /test/route
could be redirected to /test/route/
if it is defined as a path.
This can also simplify routeArguments()
so that it doesn't have to figure out if it's a hash path a second time.
How should I go about including one instance of an element on all of my pages? Including it in the index (with app router) does not work (page does not load), and including it in the layout makes multiple instances.
It would be really cool if we could set element attributes on the <app-route>
tag, like this:
<app-route path="/post/first" import="components/x-post.html" element-attributes="post-id: 1"></app-route>
<app-route path="/post/latest" import="components/x-post.html" element-attributes="post-id: 10"></app-route>
components/x-post.html
:
<polymer-element name="x-post" attributes="post-id">
Besides being a good feature on its own, this would make working with the new core-animated-pages
integration much nicer if animations in which relative section position matters are used, such as slide-from-right
.
I'm trying to pass data to a component defined in an app-route but I seem to be unable to do it.
Below are a few of the many things I've tried. I've verified that navConfig is correctly passed into core-foo. But it's always undefined in core-bar.
<polymer-element name="core-foo" attributes="navConfig">
<template>
<app-route path="/bar">
<template>
<core-bar navConfig="{{navConfig}}"></core-bar>
</template>
</app-route>
<app-route path="/bar">
<template bind="{{navConfig}}">
<core-bar navConfig="{{navConfig}}"></core-bar>
</template>
</app-route>
<app-route path="/bar" element="core-bar" navConfig="{{navConfig}}"></app-route>
</template>
</polymer-element>
Any ideas if this is possible?
Safari (on iOS and Desktop) sometimes won't load the app-router. The attachedCallback()
isn't even called.
Broken
http://erikringsmuth.github.io/app-router/
Works
http://polymer-app-router.herokuapp.com/
Not sure what's broken. It might have something to do with relative paths and HTML Imports.
Hi,
thank you for publishing this module, it sounds very promising. I'm trying to use it, and I'm facing a little issue. Here is my index.html
file using app-router:
<!doctype html>
<html>
<head>
<title>Testing<title>
<script src="./bower_components/platform/platform.js"></script>
<link rel="import" href="./bower_components/app-router/app-router.html">
<link rel="import" href="./bower_components/polymer/polymer.html">
</head>
<body unresolved>
<app-router trailingSlash="ignore">
<app-route path="/" import="./pages/home-page.html"></app-route>
<app-route path="/home" import="./pages/home-page.html"></app-route>
<app-route path="/test" import="./pages/test-page.html"></app-route>
<app-route path="*" import="./pages/not-found-page.html"></app-route>
</app-router>
</body>
</html>
My webapp is hosted under http://localhost:8080/test/index.html,
and my concern is about getting the home page in those cases:
Cases 1-2-5-6 give me the home-page (as expected)
Cases 3-4-7-8 give me the not-found-page (not expected)
Do you have any clue why isn't app-router targeting the home page when no hash is given?
FYI my server.js is:
var express = require('express')
, bodyParser = require('body-parser')
, errorHandler = require('errorhandler')
, methodOverride = require('method-override');
var port = parseInt(process.env.PORT, 10) || 8080;
var rootdir = __dirname + '/www';
var app = express();
app.get('/', function (req, res) {
res.redirect('/test/index.html');
});
app.use(methodOverride());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static(rootdir));
app.use(errorHandler({
dumpExceptions: true,
showStack: true
}));
console.log('Portal server listening on port', port);
app.listen(port);
Thank you,
Jk.
Would you consider to port this to Dart?
I don't know if the feature is available, but I would like to use the app-router not from the root of my domain.
Ex.
www.mydomain.com/sub/folder/index.html as root
When I use it now, it automatically search for the index.html in the root (www.mydomain.com/index.html)
The document.querySelector('app-router').init()
method can't be called in FireFox or IE.
Error:
document.querySelector('app-router').init is not a function
I'm not sure if public methods need to be set up differently or if this is a problem with the platform polyfill.
At build, test, and debug it says to:
Manually run functional tests in the browser by starting a static content server
(node http-server or python -m SimpleHTTPServer) and open
http://localhost:8080/tests/functional-tests/
But this gets you a 404. Perhaps you meant
http://localhost:8080/tests/SpecRunner.html
But I think this is taken care of by gulp.
Hello,
I would suggest to leave in a comment in the javascript source that is left in the minimized file. I don't have gulp installed, but I would guess that just adding the following line
// @license Copyright (C) 2014 Erik Ringsmuth - MIT license
in src/app-router.js can do it (if not, then some fiddling with gulp options are needed). Then your copyright gets automatically included in vulcanized projects which include app-router.
Is there a way to nest routes like there is in other routing solutions? Kind of like
https://github.com/polymore/more-routing
This is useful so that the children do not need to specify the full path of their parents route.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.