Passing an object when navigating in Durandal - durandal

I'm using Durandal 2.0. I have a search view and want to pass the selected item to a detail view. I know how to pass the Id, but since the search in this case has the whole object, I"d like to pass the object when navigating. I thought I could use a route with a splat, but I'm not sure how to send it when I activate the route.
The route is mapped as:
router.map([
{ route: '', title: 'Search', moduleId: 'viewmodels/search', nav: true },
{ route: 'create', title: 'Add', moduleId: 'viewmodels/create', nav: true },
{ route: 'details*movie', title: 'Details', moduleId: 'viewmodels/details', nav: false },
{ route: 'edit', title: 'Edit', moduleId: 'viewmodels/edit', nav: false }
]).buildNavigationModel();
The search view model navigates like this:
var openmovie = function (data) {
router.navigate('details*'+ ??what do I do here??);
};
And the detail view model has an activate function:
var activate = function(data) {
???what will the data be???
return true;
};

What you are trying to do is not possible using the navigation of the router. When you navigate, the router internally changes the URL so the composition lifecycle is triggered. So basically in order to make this work you would need to add all the object in the URL query string, which is something that could be pretty ugly..
Take a look at this very completed answer by Jonathan Curtis in the DurandalJS Google Groups on how to aproach this scenario.
The three approaches suggested are:
Parent-Child
EventAggregator
Shared Context (or model)

Related

Render different view dynamically in Aurelia

Is there any way in aurelia I can render different view dynamically.
async Activate(booking) {
//booking: is the route param
const hasRecord = await this.service.RecordExists(booking);
if (hasRecord) {
map(booking,form);
}
return {
//Render different template
}
}
You should try to tackle this issue in another way. Why would you want to navigate to a ViewModel and trigger its creation, just in order to not use it and load another ViewModel? Seems inefficient at best right?
Aurelia exposes pipelines on the router, you should do this check there and redirect accordingly. Look at the PreActivate step here, you could write something like this (pseudo code):
configureRouter(config, router) {
function step() {
return step.run;
}
step.run = async (navigationInstruction, next) => {
if(await this.service.RecordExists(navigationInstruction.queryParams...)
{
return next()
} else {
next.cancel(new Redirect('your other page'))
}
};
config.addPreActivateStep(step)
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
{ route: 'files/*path', name: 'files', moduleId: 'files/index', href:'#files', nav: true }
]);
}
EDIT
You can have cases where you don't want a redirect, for example you have users wanting to bookmark baseurl/businessobject/id, and the url is navigatable before the object actually exists
Then you can use the getViewStrategy() function on your ViewModel:
getViewStrategy(){
if(this.businessObj){
return 'existingObjectView.html';
} else {
return 'nonExisting.html';
}
}

Durandal child router setup with relative moduleId

I am trying to configure Durandal child routing. I have a public section for witch the parent router is responsible:
//main router
return router.map(config.publicRoutes)
.buildNavigationModel()
.mapUnknownRoutes('account/login', '#login/')
.activate();
//public routes
[{ route: 'login', title: 'Login', moduleId: 'account/login', nav: false, hash: '#login/' },
{ route: 'register', title: 'Register', moduleId: 'account/register', nav: false, hash: '#register/' },
{ route: 'reset-password', title: 'Reset password', moduleId: 'account/reset-password', nav: false, hash: '#reset-password/' },
{ route: 'private*details', moduleId: 'private/private-shell', title: 'Application', nav: true, hash: '#private/' }
];
Then a child router should be responsible for the private section. I am mapping the routes for the child router after the user has logged in. Depending on the user type (admin, user) I am activating the child router with the appropriate routes:
//initializing the router from the login view
var promise = Q.all([private_shell.initRoutes(isAdmin || true)]);
return promise.then(navigate("#private/silos"));
// child router in private-shell
var privateRouter = router.createChildRouter();
var routes = [];
//method to initialize the proper routes after login
var initRoutes = function (isAdmin) {
privateRouter.reset().makeRelative({
moduleId: 'viewmodels/private/',
fromParent: true
});
console.log(privateRouter);
return privateRouter.map(isAdmin ? config.adminRoutes : config.userRoutes).buildNavigationModel();
};
The first time when the router is initialized all works fine but if I return to the main router(login view) and another login is performed the child router adds the relative moduleId twice.
After the first login the routes have the moduleId ´viewmodels/private/route´, which is the right one, but second time the login initializes the child router the routes have the moduleId ´viewmodels/private/viewmodels/private/route´.
GET http://localhost:7777/App/viewmodels/private/viewmodels/private/silos.js 404 (Not Found)
When it should be:
GET http://localhost:7777/App/viewmodels/private/silos.js
I wasn't able to identify what might cause this. Any help?
Can you try specifying the parent route in a route property on the makeRelative settings object?
Perhaps also try making the reset call explicit.
Like this:
privateRouter.reset();
privateRouter.makeRelative({
moduleId: 'viewmodels/private/',
fromParent: true,
route: 'viewmodels/private'
});

How to set a Splat route as Default route Durandal 2.0

My default route has child views, can I set a Splat route as Default route in Durandal 2.0 if yes how I tried something like below but it fails , basically I want to implement a childrouter in my default view how can I do this..
define(['plugins/router'], function (router) {
return {
router: router,
activate: function () {
return router.map([
{ route: 'knockout-samples*details', moduleId: 'ko/index',title: 'Knockout Samples', nav: true, hash: '#knockout-samples' }
]).buildNavigationModel()
.activate();
}
};
});
If I understand you correctly then yes - you can have a splat as your default route. You would do something like this in your root shell:
router.map({
moduleId: "child/shell",
route: "*details"
});
And then in your child's view model:
var childRouter = rootRouter
.createChildRouter()
.makeRelative({ moduleId: "child" });
// Uses "child/defaultPage" as the view model, and "#/" as the route
childRouter.map({
moduleId: "defaultPage",
route: ""
});
Hope that helps.

Multiple start pages in Durandal

I have included my main.js and shell.js below for reference. As you can see my default route in the shell.js is the viewmodels/search and it has a second route to viewmodels/application with can take an option parameter, which is the IDKey for a particular application. Most of the time this is how I want users to enter the system by starting with the search screen where they can search for a particular application or have the option to click a button to start a new application. However I would like to be able to publish url links that could skip the search page and start the application with the viewmodels/application page with the appropriate IDKey.
I just cannot seem to figure out how to implement this behaviour. Can anybody get me pointed in the right direction of how to implement this.
MAIN.JS
define('jquery', [], function () { return jQuery; });
define('knockout', [], function () { return ko; });
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], function (system, app, viewLocator) {
app.title = 'My App';
//specify which plugins to install and their configuration
app.configurePlugins({
router: true,
dialog: true,
widget: {
kinds: ['expander']
}
});
app.start().then(function () {
toastr.options.positionClass = 'toast-bottom-right';
toastr.options.backgroundpositionClass = 'toast-bottom-right';
viewLocator.useConvention();
app.setRoot('viewmodels/shell', 'entrance');
});
});
SHELL.JS
define(['plugins/router'], function (router) {
return {
router: router,
activate: function () {
return router.map([
{ route: '', moduleId: 'viewmodels/search', title: 'Permit Application Search', nav: true },
{ route: 'application(/:id)', moduleId: 'viewmodels/application', title: 'Permit Application', nav: true }
]).buildNavigationModel()
.activate();
}
};
});
Following your routes as shown in code, you should simply be able to publish a link like http://yourdomain.com#application/12

Combining parameters, splat routes and child routers

We are trying to use route parameters alongside splat routes. This is the scenario.
We have a navigation menu, and then as part of one of the options, we have a wizard. When we go to the wizard, we want to pass the identifier for the object we are working on. At the same, the wizard has steps and we want to handle those at the child level router, passing also the step as part of the route (*details).
The splat route looks like this:
{ route: 'offer/:offerId/*details', moduleId: 'offerbuilder/index', title: 'Offer Builder' }
The children routes look like this:
[
{ route: 'parameters', moduleId: 'parameters', title: 'Parameters', nav: true },
{ route: 'cart', moduleId: 'cart', title: 'Cart', nav: true },
{ route: 'workspaces', moduleId: 'workspaces', title: 'Workspaces', nav: true }
]
We've got the router and child router to resolve the routes appropriately, but the issue we are having is that the hashes for the children routes don't replace the passed parameter value, but use the original pattern. In other words, this do work:
http://oursite/main/#offer/123456789/parameters
but the hashes generated as part of the wizard steps are:
http://oursite/main/#offer/:offerId/parameters
My question is, do splat routes support the inclusion of parameters? If not, what would be the suggested workaround?
This is currently an issue with the child router, which is discussed here.
The solution is to pull the parentId and manually construct the hashes for the child router.
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'employees/doctors',
route: 'doctors/:id',
});
return {
router: childRouter,
activate: function() {
var doctorID = router.activeInstruction().params;
childRouter.map([
{ route: '', moduleId: 'patients/index', hash: '#employees/doctors' + doctorID(), nav: true },
{ route: 'schedule', moduleId: 'schedule/index', hash: '#employees/doctors/' + doctorID() + '/schedule', nav: true }
]).buildNavigationModel();
}
};