Turn off history for Durandal child router - javascript

I'm using the child routers in Durandal for tab control.
However whenever you change tab, it creates a history event. Again and again. It has made my "Close" button pointless because I cannot router.navigateBack() anymore (would need to many times).
Is there a way to navigate back beyond the current parent route, or make the child router create no history?
var childRouter = m_router
.createChildRouter()
.makeRelative({ moduleId: 'viewmodels/manage/bill/center', fromParent: true })
.map([
{ route: ['contract', ''], moduleId: 'contract', title: 'Contracts', nav: true },
{ route: 'job', moduleId: 'job', title: 'Jobs', nav: true },
{ route: 'order', moduleId: 'order', title: 'Orders', nav: true }
]).buildNavigationModel();
Thanks.

It sounds like what you want to do is just to not place an entry into history. You can do this like so -
router.navigate('your/hash/here', { replace: true, trigger: false });
When your child router is making a route change.
http://durandaljs.com/documentation/Using-The-Router.html#triggering-navigation

Related

router navigation: Outlet is not activated

I'm trying to update my app to use the new v3 router than the angular team just announced http://angularjs.blogspot.com.au/2016/06/improvements-coming-for-routing-in.html and I'm having issues navigating between pages in my app, here are my routes
{ path: '/list', component: UserListComponent, index: true},
{ path: '/payrates', component: AdjustPayrateComponent },
{ path: '/assign', component: AssignUserComponent },
{ path: '/edit/:userId', component: EditUserComponent },
{
path: '/documents',
component: DocumentComponent,
children: [
{ path: '/', component: DocumentComponent, index: true },
{ path: '/:id', component: DocumentComponent },
{ path: '/upload', component: DocumentUploadComponent }
]
},
I want to navigate from the /list page to the /documents/:id page to view documents from a user on the list page.
viewDocuments(user: UserList) {
this.router.navigate(['/documents', { id: user.UserId }], { relativeTo: this.route });
}
However I'm getting the error 'Outlet is not activated' and not sure what this means. I'm basing my app off the example at http://plnkr.co/edit/ER0tf8fpGHZiuVWB7Q07?p=preview
If anyone has any example or advice on how to do navigation with the new router anything would be appreciated, thanks!
figured out the problem, I had a leftover <router-outlet> directive inside my list page, and I guess when trying to navigate from that page the router was trying to load into that bad outlet.

Best Practice: Handle read-only data with Redux

I am building an application with React and Redux and I have a question about design.
My application uses React Router. It has a Navigation Bar (on the left) that displays the routes from React Router configuration file with a Material Design Menu.
I would know what is the best practice to handle the static data of this LeftNav component.
This data has the following shape:
const menuItems = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
To respect the smart and dumb component proposal, my LeftNav component is dumb and stateless.
I also have a smart component (right now it's just my AppContainer) that renders the LeftNav component and provides the menuItems array to it via props.
I wonder if I must include this read-only data into my redux state tree. In this case, I would have a constant reducer like this:
export default handleActions({}, [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
]);
Is it a good practice to have a constant reducer with no action handler? If not, what should I do?
Thanks in advance.
I'm not recognizing how your reducer is working. What is handleActions?
I think a constant reducer seems like a reasonable solution, but I would implement it by using a default first parameter. Assuming you're using combineReducers to scope all your reducers to the part of the state they control, I'd do something like:
/* reducers.js */
const menuItemsInitialState = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
export function menuItems(state = menuItemsInitialState, _) {
return state;
}
Personally I prefer my reducers to focus on dealing with application / system-wide state. The configuration in your example would feel more at home living as a constant inside the component which makes use of it (LeftNav?)
#Cnode I agree #acjay that it should be in reducer state, even without action creators.
I would go one step further and suggest that you should fetch the data after the app loads rather than including the data in your built files. Generally data does not belong in built files.
This is because, in the real world this particular set (and others - possibly much larger sets) would be statically available on an API as you probably would want the values configurable by a CMS.
Add a static reducer to host this state
Fetch the data after the app initialises and then push it on state - meaning you will have at least one action creator.

Where do child navigation options appear from Durandal's createChildRouter?

I am trying to figure out how to properly use createChildRouter. After going in circles for a day and a half trying to find code that will give me some results, I have settled for the moment on the following code (which still doesn't give me results):
shell.js
var routes = [
{ route: '', moduleId: 'home', title: 'Home', nav: 1 },
{ route: 'inventory/*index', moduleId: 'inventory/inventory', title: 'Inventory', nav: 2 }];
return router.makeRelative({ moduleId: 'viewmodels' }) // router will look here for viewmodels by convention
.map(routes) // Map the routes
.buildNavigationModel() // Finds all nav routes and readies them
.activate(); // Activate the router
inventory.js
define(['services/logger', 'plugins/router'], function (logger, router) {
var title = 'Details';
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'viewmodels/inventory',
fromParent: true
}).map([
{ route: 'index', moduleId: 'inventory', title: 'Inventory', nav: 3 },
{ route: 'inventory', moduleId: 'items', title: 'Items', nav: 4 }])
.buildNavigationModel();
var vm = {
router: childRouter,
activate: activate,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
}
//#endregion
});
My tree structure looks like this:
Because of my failure to get any results, I'm starting to suspect that it's not my routing code that is the problem, but rather my navigation menu or perception of how/whether child routers are supposed to appear and be visible without additional work. I'm using a plain vanilla template (hot towel) and the only thing I've changed is the logging code in router.js because I'm trying to figure out what the router code is looking for. The result I get from the above code (with my modified logging) is as follows when I click on the button to switch from my "Home" view to my "Inventory" view:
LOG: /^$/ != inventory/*index
LOG: /^inventory\/(.*?)$/ == inventory/*index
LOG: Activating[object Object]
LOG: [Details] Details View Activated
LOG: Navigation Complete[object Object][object Object]
LOG: /^index$/ != *index
LOG: /^inventory$/ != *index
LOG: Route '*index' Not Found
So my questions are, is this what the routing code is supposed to do? Is it supposed to fail to match the splat route with any of the child routes? Do I need to do something to make navigation buttons for the child routes appear? Were they supposed to appear automatically? My view does switch to inventory.js/html, but no new navigation buttons appear when this happens.
What am I missing?
Check out knockout samples for a working implementation of child routes. Child routes will NOT be added to the primary navigation if that's what you're expecting. They are intended to be used with a secondary navigation. The HTML version can be seen live at http://dfiddle.github.io/dFiddle-2.0/#knockout-samples.

Durandal 2.0 "routes"

I'm a Beginner trying to learn SPA / Durandal, Knockout etc...
Need some help with my routes for a Admin drop down button.
Here are my routes in the shell.js so far:
var routes = [
{ route: '', moduleId: 'home', title: 'Home', nav: 1 },
{ route: 'downtime', moduleId: 'downtime', title: 'Downtime', nav: 2 },
{ route: 'downtimeadd', moduleId: 'downtimeadd', title: 'Add A New Downtime', nav: false, settings: { admin: true } },
{ route: 'production', moduleId: 'production', title: 'Production', nav: 4 }];
I've also created adminRoutes in the shell.js to bind to the view with KO:
var adminRoutes = ko.computed(function () {
return router.routes.filter(function (r) {
return r.settings.admin;
});
});
From research, they say that router.routes is an array, but when I bind this to my view the button shows 0 items for the drop down.
When I do (below) I can get all the routes, but I only need the admin routes...
var adminRoutes = ko.computed(function () {
return router.routes;
});
How should I proceed? It seems like router.routes is not actually an array?
If I try to print it out to the console:
console.log(router.routes[0]); //Chrome says its undefined...
console.log(router.routes); //Shows array of size 4...
Yup no clue... Help would be appreciated!
Update:-------------------------------------------------------------------
Even after RainerAtSpirit's suggestions I still get an empty array when I filter in code.
var router = require('plugins/router');
//Array size 4
console.log(router.routes);
//Array size 0
console.log(router.routes.filter(function (r) { return r; }));
However when I run this in the "chrome console":
var router = require('plugins/router')
router.routes.filter(function (r) { return r; })
I do get the array back, so I don't know why in code it doesn't work.
I got this to work, although maybe not as perfectly as I would like.
The first problem I noticed was an issue that others were not seeing. In my example (with druandal 2.0 from the HotTowel template version 1.1 for VS 2013) I noticed that this code for adding the adminRoutes was called prior to the activate method. Therefore, my route.routes was an empty array. To fix this problem, I switched to just using the array of routes that the routes are getting mapped from the config.js with (note: config.routes rather than route.routes):
var adminRoutes = ko.computed(function () {
return config.routes.filter(function(r) {
return r.admin;
});
});
Finally, I'm return "r.admin". This is the part that would like to make work better because I added an admin property to the admin route without the "settings" group, see "admin: true":
var routes = [
{ route: '', moduleId: 'equipment', title: 'Equipment', nav: 1 },
{ route: 'testresults', moduleId: 'testresults', title: 'Test Results', nav: 2 },
{ route: 'testresultdetail/:id', moduleId: 'testresultdetail', title: 'View a test result', nav: false },
{ route: 'testresultadd', moduleId: 'testresultadd', title: 'Add a Test Result', nav: false, caption: '<i class="fa fa-plus"></i> Add Test Result', admin: true }
];
With these two changes, the menu item showed up.
In knockout when you make a property to be observable it turns in to a function wrapping ,so when you need to get the actual value you need to access it as a function. That means
router.routes[0] will be undefined but router.routes()[0] will work correct !
You can try
var adminRoutes = ko.computed(function () {
return router.routes().filter(function (r) {
return r.settings.admin;
});
});

HotTowel Durandal Inject different views based on the user

In the shell.html for HotTowel template we have:
<!--ko compose: {model: router.activeItem,
afterCompose: router.afterCompose,
transition: 'entrance'} -->
<!--/ko-->
which will automatically insert the proper view by convention. I am trying to inject different views based on the user's role in a HotTowel/Durandal App. For example,
I have two Views,
productEditor_Admin.html
productEditor_Superviser.html
(instead of these two views, I used to have only productEditor.html, by convention everything worked)
and only a single ViewModel:
productEditor.js
Now, I want to have a function in productEditor.js that will let me decide which view to insert based on user's role. I see in the Composition documentation, we can do function strategy(settings) : promise but I am not sure what's the best way to accomplish this in the HotTowel template. Anyone have already tried and got an answer for that?
It's possible to return a 'viewUrl' property in the view model, so hopefully something like the following will crack the door open ;-).
define(function () {
viewUrl = function () {
var role = 'role2'; //Hardcoded for demo
var roleViewMap = {
'default': 'samples/viewComposition/dFiddle/index.html',
role1: 'samples/viewComposition/dFiddle/role1.html',
role2: 'samples/viewComposition/dFiddle/role2.html'
};
return roleViewMap[role];
}
return {
viewUrl: viewUrl(),
propertyOne: 'This is a databound property from the root context.',
propertyTwo: 'This property demonstrates that binding contexts flow through composed views.'
};
});
Did you take a look at John Papa's JumpStart course on PluralSight.
Look at the source code from that app here: https://github.com/johnpapa/PluralsightSpaJumpStartFinal
In App/Config.js file he adds other routes which are visible by default as :
var routes = [{
url: 'sessions',
moduleId: 'viewmodels/sessions',
name: 'Sessions',
visible: true,
caption: 'Sessions',
settings: { caption: '<i class="icon-book"></i> Sessions' }
}, {
url: 'speakers',
moduleId: 'viewmodels/speakers',
name: 'Speakers',
caption: 'Speakers',
visible: true,
settings: { caption: '<i class="icon-user"></i> Speakers' }
}, {
url: 'sessiondetail/:id',
moduleId: 'viewmodels/sessiondetail',
name: 'Edit Session',
caption: 'Edit Session',
visible: false
}, {
url: 'sessionadd',
moduleId: 'viewmodels/sessionadd',
name: 'Add Session',
visible: false,
caption: 'Add Session',
settings: { admin: true, caption: '<i class="icon-plus"></i> Add Session' }
}];
You can add routes to both the views here using the same logic and then in your productEditor.js you can decide which view to navigate and navigate to that using router.navigateTo() method.

Categories

Resources