I am trying to build dynamic routes for an admin section on my site so that "/admin" would work as well as "/admin/users" and "/admin/users/add" and so on. I have tried some different combinations but still struggling with this. Below is what I've tried and in different orderes.
Ideally if I could just specify "/admin" but dynamically reference each new / as an argument that would be best for handling in the code. Example "/admin/1/2/3/4/5" and being able to reference the 1 2 3 etc. I didn't see anything like this in the docs though.
Router.route('/admin', {
name: 'admin',
path: '/admin',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', 'dashboard');
Session.set('asect', null);
this.render();
}
});
// Not Working...
Router.route('/admin/:apage', {
name: 'admin',
path: '/admin/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', this.params.apage);
Session.set('asect', null);
this.render();
}
});
// Not Working...
Router.route('/admin/:apage/:asect', {
name: 'admin',
path: '/admin/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', this.params.apage);
Session.set('asect', this.params.asect);
this.render();
}
});
EDIT (Answered)
After some testing it seems calling a template should be (or easiest done) in the this.render() line and the routes should go from most restrictive/detailed to least - which I did try before. The problem seems to be using this.params on the template: line. This solution is not perfect but posting for anyone who may run into a similar problem. As far as further variables in the url like "/admin/1/2/3/4/5" it seems they would need additional routes and can't be fully dynamic as a "/" can not go into the params and the router would look for a route and return notFound unless you can an explicit matching route. There may be a work around that I did not find.
Working code below:
Router.route('adminPage', {
path: '/admin/:asect/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', this.params.asect);
Session.set('apage', this.params.apage);
this.render('admin_' + this.params.asect + '_' + this.params.apage);
}
});
Router.route('adminSect', {
path: '/admin/:asect',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', this.params.asect);
Session.set('apage', null);
this.render('admin_' + this.params.asect);
}
});
Router.route('admin', {
path: '/admin',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', 'dashboard');
Session.set('apage', null);
this.render('admin_dashboard');
}
});
There is a way to have optional parameters in routes (which is what you're looking for unless I'm mistaken). With that in mind, you should be able to manage using one router.
Router.route('admin',{
path: '/admin/:asect?/:apage?',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
var asect = this.params.asect || 'dashboard',
apage = this.params.apage || null,
render = (function(){
if(apage !== null) {
return 'admin_'+ asect +'_'+ apage;
} else {
return 'admin_'+ asect;
}
})();
Session.set('asect', asect);
Session.set('apage', apage);
this.render(render);
}
});
The ? after each parameter in the path designates it as an optional parameter. You should be able to then check if it has been defined or otherwise assign a default value and then structure your view and session accordingly.
Note: You can test in this MeteorPad - just update the URL according to the names of the example templates.
http://meteorpad.com/pad/Ri4Np5xDJXyjiQ4fG
Related
I'm working in a small Angularjs 1.5 project and I'm quite new.
I have to create an object or edit an existing object and the template is the same, the only difference in the logic is the call to an http service for data retrieve in case of update.
I use ngRoute.
How can I do to use the same component for both operations ?
EDIT
JS COMPONENT CODE
angular.
module('editProject').
component('editProject', {
templateUrl: 'app/edit-project/edit-project.template.html',
controller:['$http','$routeParams', function EditProjectController($http,$routeParams) {
var self=this;
$http.get('rest/projects/' + $routeParams.id ).then(function(response) {
console.log(JSON.stringify(response.data));
self.project = response.data;
});
}
]
});
ROUTE CONFIG
angular.
module('myApp').
config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');
$routeProvider.
when('/projects', {
template: '<project-list></project-list>'
}).
when('/projects/:id', {
template: '<project-detail></project-detail>'
}).
when('/projects/edit/:id', {
template: '<edit-project></edit-project>'
}).
when('/projects/new', {
template: '<edit-project></edit-project>'
}).
otherwise('/projects');
}
]);
1. Quick fix
Simple solution would be using a conditional checking if the $routeParams.id param is defined, if so, then it need a request to feed the project informations, otherwise not.
if($routeParams.id){
$http.get('rest/projects/' + $routeParams.id ).then(function(response) {
console.log(JSON.stringify(response.data));
self.project = response.data;
});
}
2. Component Router
Even though the previous solution looks simple and functional, it might not be the best. A propper solution is to use a separate component for each route, but you can reuse the form part of it. Also, assuming that you are building a completelly component-based app you should use ngComponentRoute instead of ngRoute.
.component('app', {
template: '<ng-outlet></ng-outlet>',
$routeConfig: [
{path: '/projects', name: 'Projects', component: 'projectList', useAsDefault: true},
{path: '/projects/:id', name: 'Project Detail', component: 'projectDetail' },
{path: '/projects/edit/:id', name: 'Edit Project', component: 'editProject' },
{path: '/projects/new', name: 'New Project', component: 'newProject' }
]
});
Then you can create a project editor component and reuse it on both edit and new project page by just adding <project-editor-form project="{}" on-save="$ctrl.mySave()"></project-editor-form>. For example:
.component('projectEditorForm', {
template:
'Project Name: <input type="text" ng-model="$ctrl.project.name">' +
'<button ng-click="$ctrl.onSaveProject($ctrl.project)">Save</button>',
bindings: {
project: '<',
onSave: '&'
},
controller: function () {
var $ctrl = this;
$ctrl.onSaveProject = function (project) {
// general save logic goes here
// if you want to reuse this too
// then emit the on save for onSave binding
$ctrl.onSave($ctrl.project);
}
},
})
.component('editProject', {
template:
'<project-editor-form project="$ctrl.project" on-save="$ctrl.mySave()">' +
'</project-editor-form>',
bindings: {
project: '<',
onSave: '&'
},
controller: function ($http) {
var $ctrl = this;
// consider using a Service to do such task
// instead of request directly from the controller
$http.get('rest/projects/' + $routeParams.id).then(function(response) {
$ctrl.project = response.data;
});
$ctrl.mySave = function (project) {
// save logic here
}
},
})
3. ngRoute with resolve
Another approach that doesn't deppend on ngComponentRoute, is to use the route resolve property, it's very usefull when using component as route template. You add a resolve property to your route, that would be the project, and bind to your form component.
$routeProvider
.when('/projects/edit/:id', {
template: '<project-editor-form project="$resolve.project"></project-editor-form>',
resolve: {
project: function ($route, $q) {
var id = $route.current.params.id;
// again, consider using a service instead
return $http.get('rest/projects/' + id).then(function (response) {
return response.data;
});
}
}
})
.when('/projects/new', {
template: '<project-editor-form project="$resolve.project"></project-editor-form>',
resolve: {
project: {
id: 0,
name: ''
}
}
})
I wish I can give a more detailed response, but I can't even figure out how to debug this situation.
The situation: When I click on the button, the URL changes for a split second to http://localhost:3000/register. Nothing new gets rendered, the page stays the same as if I didn't click anything. Things I've tried:
Typing in Router.go('/register') in the console works properly
I have tried this with every single route, not just the /register route
I have tried this in both Firefox and Chrome
This is not an issue if I use {{pathFor 'templateName'}} in the template, only when I use Router.go('path')
I have a link in my header to register:
Template.header.events({
'click .register-btn': function(e) {
Router.go('/register');
}
});
And, of course, I have this in my template:
Contact
Here is my entire routing file, router.js:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound'
});
// Routes
Router.route('/', {
name: 'landingPage', // Main domain routing
layoutTemplate: 'landingLayout',
onBeforeAction: function (pause) {
if (Meteor.user()) {
Router.go('/home');
} else {
this.next();
}
}
});
Router.route('/home', {
name: 'siteHome',
onBeforeAction: function() {
if (this.ready()){
if (!Meteor.loggingIn())
AccountsTemplates.ensureSignedIn.call(this);
}else{
// render the loading template but keep the url in the browser the same
this.render('loading');
this.renderRegions();
}
}
});
Router.route('/weekly_goal/set', {
name: 'setWeeklyGoal'
});
Router.route('/make_entry', {
name: 'makeEntry'
});
// Login and Register Routes
AccountsTemplates.configureRoute('signIn', {
name: 'login',
path: '/login',
template: 'siteLogin',
});
AccountsTemplates.configureRoute('signUp', {
name: 'register',
path: '/register',
template: 'siteRegister',
redirect: '/',
});
When you click a button inside of a form, the default browser behavior is to submit the form which causes another HTTP request and reloads the page. In order to avoid this, your event handler needs to explicitly prevent the default behavior:
Template.header.events({
'click .register-btn': function(e) {
e.preventDefault();
Router.go('/register');
}
});
I have a requirement to make what is essentially a dynamic form (wizard) that has multiple steps. Now I want to be able to quickly add new steps to the wizard in the future (or remove them) so I don;t to create separate routes for each step like so:
this.resource('wizard', { path: '/' }, function() {
this.route('step1', { path: '/' });
this.route('step2');
this.route('step3');
this.route('step4');
this.route('step5');
});
I would much prefer to have a dynamic segment that takes in the name of the step and loads the corresponding template of the same name, like so
this.resource('wizard', { path: '/' }, function() {
this.route('step', { path: '/:step' });
});
Is this at all possible or is this just wishful thinking.
I have come up with a solution but I am not sure it is considered the best...
I have defined a route in the router to take in a dynamic segment with the name of the template:
this.resource('wizard', { path: '/wizard' }, function() {
this.route('missing', { path: '/:step' });
});
I have then created a missing route that takes this dynamic segment from the model and uses it to load in the template into the appropriate outlet
export default Ember.Route.extend({
renderTemplate: function(controller, model) {
this.render('wizard/' + model.step, {
controller: controller
});
}
});
I would love to hear some thoughts on this solution.
I have a super controller: ApplicationController which yields to my regions: header/footer. When one of my configured routes attempts to yield to another region on another template, the parent controller's yieldTemplates are overridden.
Example:
ApplicationController = RouteController.extend({
yieldTemplates: {
'footer': { to: 'footer' },
'header': {to: 'header'}
}
});
var SignUpController = ApplicationController.extend({
template: 'signUp'
});
Router.map(function () {
this.route('signup', {
path: '/sign-up',
controller: SignUpController,
template: 'signUp-form',
disableProgress: true,
yieldTemplates: {
'personal-signup': {to: 'signup-detail'}
}
});
});
Any idea why inheritance isn't working in this situation ?
I had a similar issue, read the answer here: https://github.com/EventedMind/iron-router/issues/249#issuecomment-27177558
What's happening is your Router level config is overriding the RouteController prototype. In general, options override prototype properties in iron-router.
A simple solution is to create a global object with the main yields, and then extend that object on each Controller when new yields are necessary:
var mainYieldTemplates = {
'footer': { to: 'footer' },
'header': {to: 'header'}
};
ApplicationController = RouteController.extend({
yieldTemplates: mainYieldTemplates
});
var SignUpController = ApplicationController.extend({
template: 'signUp',
yieldTemplates: _.extend({}, mainYieldTemplates, {
'personal-signup': {to: 'signup-detail'}
}
)
});
Router.map(function () {
this.route('signup', {
path: '/sign-up',
controller: SignUpController,
template: 'signUp-form',
disableProgress: true,
});
});
There is also a minor inconsistency in your code where you declare the "template" property to be "signUp" in the controller, but then in the route itself you set it to "signUp-form". This will overwrite the "template" property on the controller. You can instead create a new controller for each route with all of the route's properties instead of overwriting them.
I got sick of using the solution in the other answer and just ended up monkeypatching RouteController to behave as I'd expect in this case. Works great so far.
(function(){
var orig = RouteController.extend;
RouteController.extend = function(newChild) {
var extendedTemplates = {};
if (!newChild.yieldTemplates) {
newChild.yieldTemplates = {};
}
_.extend(extendedTemplates, this.prototype.yieldTemplates);
_.extend(extendedTemplates, newChild.yieldTemplates);
newChild.yieldTemplates = extendedTemplates;
return orig.apply(this, arguments);
}
})();
Throw that code somewhere before you create any RouteControllers of your own. Any further controllers extended from there will extend onto the yieldTemplates field of the parent.
I have started learning the ember.js framework and I am stuck at how to use the setting of the URL type feature that the framework has.
http://emberjs.com/guides/routing/specifying-the-location-api/
I have this simple application.js
App = Ember.Application.create();
App.Router.reopen({
location: 'history'
});
App.Router.map(function () {
this.route('about');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return appdata;
}
});
App.IndexRoute = Ember.Route.extend({
setupController: function (controller) {
// Set the IndexController's `title`
controller.set('indextitle', "My Index title");
},
renderTemplate: function () {
this.render({ outlet: 'indexoutlet' });
}
});
App.AboutRoute = Ember.Route.extend({
model: function () {
return appdata;
},
renderTemplate: function () {
this.render({ outlet: 'aboutoutlet' });
}
});
var appdata = { mytext: '', theplaceholder: 'Enter new text', attr:'Yeap!' }
If I don't use the
App.Router.reopen({
location: 'history'
});
the application works fine and it goes to the 'about' route by appending the URL the '~/EmberjsTest.aspx#/about' as it supposed to do.
However because I do not like the hash symbol in the URL of the page, I would prefer if it was removed and to do that the guide says we should put this code:
App.Router.reopen({
location: 'history'
});
But when I do it I get an error in the Chrome console saying:
'Assertion failed: The URL '/EmberjsTest.aspx' did match any routes in your application'
What am I doing wrong?
Thanks in advance
If you want to use the history API then you have two options.
Serve your Ember app from '/' so that Ember can just work with it's "normal" index/root route.
Create a route in your Ember app that can handle '/EmberjsTest.aspx'.
this.route("index", { path: "/EmberjsTest.aspx" });
Note that if you go with option 2 you'll probably have to update all of your routes to include '/EmberjsTest.aspx' in their paths.
this.resource("posts", {path: "/EmberjsTest.aspx/posts" })