angular-light
angular-light copied to clipboard
router
https://gist.github.com/fend25/3619d6d730039c30a34d
https://jsfiddle.net/fend25/e1LLu72c/ better and preferabe variant now,
guys, feel free to comment)
Haha, thought yesterday how great it would be having some views bound to routes. :D
A good basic router implementation would be this: http://krasimirtsonev.com/blog/article/A-modern-JavaScript-router-in-100-lines-history-api-pushState-hash-url
My approach would use regexes as parameter of a directive:
<div al-router-view="/main/?">
<a href="/user/1">User 1</a>
</div>
<div al-router-view="/user/:id/?">
<a href="/main">Back to overview</a>
</div>
This would allow to define the urls directly in the template, no additional code would be needed for basic features. But there should definitivly be an API to do redirects, handle url errors etc.
Oleg's reference implementation (ancient one) http://plnkr.co/edit/QYb53NUnnhIezI5n0JxB?p=preview
Ok, i just build an absolute simple, quick n dirty views implementation: https://jsfiddle.net/r9t3ropc/
Some explanation: the al-router-view directive could be extended to watch the expression. So one could define the url structure in javascript or the url of a view could be changed:
<div al-router-view="myVar.main">Main</div>
<div al-router-view="myVar.user">User</div>
scope.myVar = {
main: '/main',
user: '/user'
}
Comments are welcome.
I think it should be separated into two different API's:
- Router is for router events and state handling
- View rendering route data into template and managing scope.
The first should have unified interface to change current location and update/rewrite state. Also it should be presented in root scope and should broadcast events. Events could be stopped to prevent page reloading when there is unsaved changes or so.
View should allow to use functions as route value. This functions return async operations to load template and resource specified by route. This is needed to render state dependent routes. By default view listening scope's $routeChanged event. But changes source could be configurable to use exact router instance.
<al-app>
<al-view al-view.routes = "routes"/>
</al-app>
alight.bootstrap('al-app', {
routes : {
// Simple template route
'/': 'index.html',
// State dependent route
'/home': function(route, scope){
if (scope.user) {
return {
scope: {
user: user
},
template: '/users/home.html'
}
} else {
return '/sign-in.html'
}
},
// Async route
'/users/:id' : route => {
var user = users.get(route.id);
var promise;
if (! user) {
promise = loadUser(route.id).then(user => {
if (! user) {
// User not found, show 404 error page
return '/errors/404.html';
} else {
// User found, use user page template
return {
scope: {
user
},
template: '/users/item.html'
}
}
});
} else {
promise = Promise.resolve(user);
}
return promise;
}
}
});
Also view could has loading view with spinner to indicate intermediate state while resolving promise.
@rumkin Can I use common header and footer for "/users/*" in your approach. In this example by @fend25 I can do this: https://jsfiddle.net/fend25/e1LLu72c/
Events could be stopped to prevent page reloading when there is unsaved changes or so.
It's @onLoseFocus="showPreventWindow()" in the example.
@onSwitchTo="onSwtichToUser()" could be something like this:
scope.onSwtichToUser = route => {
var user = users.get(route.id).then
var promise;
if (! user) {
promise = loadUser(route.id).then(user => {
if (! user) {
// User not found, show 404 error page
return '/errors/404.html';
} else {
// User found, use user page template
return {
scope: {
user
},
template: '/users/item.html'
}
}
});
} else {
promise = Promise.resolve(user);
}
return promise;
}
What do you think? "Configuring" in html can be easier.
@lega911 Yep, it could be done with several view elements. Like this:
<al-view al-view-routes="routes.head">
<al-view al-view-routes="routes.body">
<al-view al-view-routes="routes.footer">
It's not so elegant as HTML configuration but it simply to manage from code. I think configuring in HTML is the most proper way to do this for MVVM app. But it should be also configurable and controllable from code.
@ is not valid HTML attribute name character. Is this some kind of future DSL?
I think it's better to configure router with html and implement fully controllable router and view interfaces with focus on usability.
@ is not valid HTML attribute name character. Is this some kind of future DSL?
Yes, but we can make it like this (with the event directive):
al-on:on-lose-focus="showPreventWindow()"
or alias
@on-lose-focus="showPreventWindow()"
and "on-lose-focus" is an event (new CustomEvent), which you can catch even with jQuery. https://github.com/lega911/angular-light/issues/123
Maybe it's better to avoid on duplication and bind events to target component to avoid mess of events in the future:
al-router:on-reload-requested
al-input:on-lost-focus
Maybe, but custom events can be more flexible.
May it possible to implement url handling like express.js? Another point is, that using Views which use "templates" would not be useful, because in this case i could redirect the user to a page which shows the content of this "template". Views should be used inline, to easily switch between views by a changing url. I also think it is not useful to bind a view to a scope variable.
My approach uses a global routes object, where regexes are defined. Using al-route or by adding routes to the object, new routes can be defined. Also a custom route can be defined in this object, which will be referenced by a given element selector.
<div id="home"></div>
<div id="players"></div>
<div al-route="/users/:user"></div>
alight.routes = {
'/home': (scope) ->
return '#home'
'/players/:id': (scope, id) ->
scope.id = id
return '#players'
}
alight.d.al.route = (scope, exp, element, env) ->
alight.routes[exp] = ->
return element
What do you think?
My approach uses a global routes object,
I agree
div id="home"></div> <div id="players"></div> <div al-route="/users/:user"></div>```
should it work like al-if with dependency on url?
What about this one?
<div>
<div al-route:group @switch-to="onSwtichToAuth()">
<div al-route="/login" al-ctrl="App"/>
<div al-route="/register" al-ctrl="App"/>
</div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/posts" al-ctrl="Posts"/>
<div al-route="/feed" al-ctrl="Feed"/>
<div al-route="/user">
<div al-ctrl="userList"></div>
<div al-route="/user/:userId" al-ctrl="userItem" @lose-focus="checkIfUserSaved()">
<div al-if="!user">Loading...</div>
<div al-if="user" al-include="/templates/userItem.html"></div>
</div>
</div>
<div al-route="*">
No page 404
</div>
<Footer/>
</div>
</div>
when url = "/user/:userId", then controller al-ctrl="userItem" is activated, there you can load a user,
a template can be loaded by al-include when a user is loaded <div al-if="user" al-include="/templates/userItem.html"></div>,
and you can see this before user is loaded <div al-if="!user">Loading...</div>
so, visible html for /user/:userid would be
<div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/user">
<div al-ctrl="userList"></div>
<div al-route="/user/:userId" al-ctrl="userItem" @lose-focus="checkIfUserSaved()">
<div al-if="!user">Loading...</div>
<div al-if="user" al-include="/templates/userItem.html"></div>
</div>
</div>
<Footer/>
</div>
</div>
visible html for /user would be
<div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/user">
<div al-ctrl="userList"></div>
</div>
<Footer/>
</div>
</div>
visible html for /login would be
<div>
<div al-route:group @switch-to="onSwtichToAuth()">
<div al-route="/login" al-ctrl="App"/>
</div>
</div>
I agree. It looks exciting. And it should to allow to load subroutes with al-include for complex components.
@rumkin al-include is deprecated, look at http://angular-light.readthedocs.org/en/latest/directive/html.html , it's more flexible :)
@lega911 I will. But you're using al-include in your example.
Let's focus on modular structure, easy of use and flexibility. And thus it should to use al-html to load subroutes.
should it work like al-if with dependency on url?
Yes, you have a pretty good understanding of what i mean. :-)
al-route:group
I do not understand how this works. I understand that it groups some requests. But how is defined that a specific request should be used? Which request is the first if you are visiting url "/"?
What i also thought about are subroutes:
<div al-route="/user">
<div al-route="/:id"></div>
<div al-route="/details"></div>
</div>
Which would be matched when accessing: /user /user/:id /user/details
Maybe it would be great to also handle redirects in the global route object:
alight.routes = {
'/home': '/'
'/players/:id': (scope, id) ->
scope.id = id
return '#players'
}
which would redirect "/home" to "/".
al-route:group I do not understand how this works.
It's easy, one rule - if there is a route that maches to url, then it should be visible, then all parent DOM should be visible, other route-elements should be hidden. I changed your example in this style:
<div al-route>
<header />
<div al-route="/profile"></div>
<div al-route="/user/:id/view"></div>
<div al-route="/user/details"></div>
<footer />
</div>
for url "/user/details" we will have:
<div al-route>
<header />
<div al-route="/user/details"></div>
<footer />
</div>
In your approach we have to copy-past header and footer for "/profile". On the other side, your approach works well for loaded html (al-html).
Hm. This is a bit confusing for me. The grouping attribute is there to render a specific header an a footer, right? The content for a route is inside of
<div al-route="/profile">
<h1>My user profile</h1>
</div>
So when accessing /profile i will see this:
<header />
<div al-route="/profile">
<h1>My user profile</h1>
</div>
<footer/>
Am i right?
So when accessing /profile i will see this: Am i right?
Yes. Actually for example above we don't need a "group" route:
<div>
<header />
<div al-route="/profile"></div>
<div al-route="/user/:id/view"></div>
<div al-route="/user/details"></div>
<footer />
</div>
In a simple, "group route" just holds all child routes, like al-route="/profile; /user/:id/view; /user/details", but it takes them automatically.
The grouping attribute is there to render a specific header an a footer, right?
Not exactly, it renders all DOM, except routes that don't match to URL
Not exactly, it renders all DOM, except routes that don't match to URL
I like the approach. :-)
Prototype: http://plnkr.co/edit/lZkXWXg5xk1J147nb1jX embeded: http://run.plnkr.co/plunks/lZkXWXg5xk1J147nb1jX/
with no regex yet.
There is an regex router with params and tail capturing: https://jsfiddle.net/rumkin/y6xLu1sm/
Here an regex router with params and tail capturing:
I took it for my example
You can get arguments using event al-on.route-to
<div al-route="/user/:name/view" @route-to="username=$event.value.name">
One more example: http://plnkr.co/edit/pQBycumCKE1DJb3oVaPE?p=preview
- you can use the same routes a few times in different places (
al-route="/user/details"in the example) - default route
<div al-route="*">
404 - Not found
</div>
You can get arguments using event
al-on.route-to
Why not providing these variables automatically in a scope variable? E.g.
scope.$route.name
Parsing from URL shouldn't be so difficult:
"/user/:id/name/:name/view/:action".match(/:\w+/g)
Why not providing these variables automatically in a scope variable?
It can give you collisions for routes "/users/:name", "/users/root" they both will be visible on url="/users/root"