Durandal Routing - routing to a resource with a particular id? - single-page-application

I'm trying to configure my routing so that navigating to #/my displays the contents for #/folder/62 (or some id stored in a variable) - and navigating to #/public displays the contents for #/folder/1 (same concept).
Additionally, I'd like the application to navigate to one of these routes upon loading, depending on whether or not the user is authenticated. The authentication stuff is done, but once the above routes have been configured, I'd like to activate on one of them.
Here's what I have:
activate: function () {
router.useConvention();
router.handleInvalidRoute = function (route, params) {
//debugger;
logger.logError("Invalid route", route, null, true);
};
router.map([
{ url: 'home', moduleId: 'viewmodels/home', name: 'Home', visible: false },
{ url: 'my', moduleId: 'viewmodels/folder', name: 'My Content', visible: false }, // Should display contents of /folder/2
{ url: 'public', moduleId: 'viewmodels/folder', name: 'Public Content', visible: false }, // Should display contents of /folder/3
{ url: 'set/:id', moduleId: 'viewmodels/set', name: 'Set', visible: false },
{ url: 'folder/:id', moduleId: 'viewmodels/folder', name: 'Folder', visible: false }
]);
if (auth.isAuthenticated)
return router.activate('my'); // should show details page of a particular folder
else {
return router.activate('public'); // should show details page of a particular folder
}
}

app.setRoot as described in https://groups.google.com/forum/#!searchin/durandaljs/app.setRoot/durandaljs/t1hrLfOh1oM/RtCekmY0bDAJ could be used to show different views for authenticated/non-authenticated users.
If only authenticated users should be allowed to see content of "folder/specialID" in the example then you might consider not to expose these via router. Use standard compose functionality in #my to load the specialID view/view model instead.

Related

How to use Aurelia's router with server-side routing

I have an Aurelia project that uses server-side routing with MVC. I am using a layout file that points to a main "app" component, like this:
<div aurelia-app="main" start="app" data-model='#Json.Encode( Model )'></div>
Each of my views contain a reference to the layout file and something like this:
<div aurelia-app="main" start="sample-module" data-model='#Json.Encode( Model )'></div>
My main.js is configured like this:
export function configure(aurelia) {
aurelia.use.standardConfiguration()
aurelia.container.registerInstance('viewModel',
Object.assign({}, JSON.parse(aurelia.host.dataset.model)));
aurelia.start().then(a => {
let start = a.host.attributes.start.value;
a.setRoot(start);
});
}
And I am leveraging Aurelia's router (in app.js) like this:
export class App {
constructor() {
}
configureRouter(config, router) {
config.title = 'Aurelia';
config.options.pushState = true;
config.options.root = '/';
config.map([
{ route: ['', 'Aurelia/Home'], name: 'home', moduleId: 'home', title: 'home', nav: false },
{ route: 'Aurelia/SampleModule', name: 'sample module', moduleId: 'sample-module', title: 'sample module', nav: true },
]);
this.router = router;
}
}
This almost works. If I navigate to SampleModule using the Aurelia navigation link, it loads the module but doesn't hit the server - doesn't even hit the View. If I navigate to Aurelia/SampleModule manually, it loads sample-module twice, including the data from the server. The Aurelia router updates the url exactly as I would expect, so if I navigate and then hit refresh it loads from the server correctly.
I want to be able to use the navigation to change the view without refreshing the entire page, but still leverage my server-side routing and hit the Views and Controllers.
I was able to solve my problem by removing the reference to the app from each of the views:
<div aurelia-app="main" start="sample-module" data-model='#Json.Encode( Model )'></div>
as well as removing the start logic from the layout file and main.js, so now my main.js looks something like this:
export function configure(aurelia) {
aurelia.use.standardConfiguration()
aurelia.container.registerInstance('viewModel',
Object.assign({}, JSON.parse(aurelia.host.dataset.model)));
aurelia.start().then(() => aurelia.setRoot());
}
In the back end, the model has a unique property to each page:
public class AppViewModel
{
public HomeViewModel Home { get; set; }
public SampleModuleViewModel SampleModule { get; set; }
}
From the view model (i.e. sample-module.js), it checks to see if the associated property (AppViewModel.SampleModule) is populated, and if not, it makes an ajax call.

Aurelia How do I use a Child Router and dynamics child routes

Trying to use a child route in Aurelia. Can't seem to get my head around the workings of nested routes. Are all routes derived from the root of the app or relative to location of the current router?
Why wont my route-href work in this example? I have a route in this router named screen and it does have an :id parameter
screens/list.ts
#inject(Router)
export class ScreensList {
heading;
router;
screens: any[];
constructor(router){
this.heading = 'Child Router';
this.router = router;
this.screens = [
{
id: 1,
name: 'my screen'
},
{
id: 2,
name: 'my other screen'
}
]
router.configure(config => {
config.map([
// dynamic routes need a href, such as href: screen
{ route: 'screen/:id', moduleId: 'screens/screen/display', name: 'screen', title: 'Screen #1' }
]);
});
}
}
List View
screens/list.html
<li repeat.for="screen of screens">
<a route-href="route: 'screen', params: { id: screen.id }"/>Screen #${screen.id}</a>
</li>
I then have a dummy VM/V at screens/screen/display.
Do I really have to specify the full filepath for a module in a nested child router. I thought it would be routes relative to the location of the parent router or at least the name (root) of the parent?
vendor-bundle.js:11582 ERROR [route-href] Error: A route with name ''screen', params: { id: screen.id }' could not be found.
Check that `name: ''screen', params: { id: screen.id }'` was specified in the route's config.
In your example, you are injecting the router, which is the router configured in app.js, and then calling its configure method. Aurelia is Convention-Over-Configuration. So, use the convention and you will be fine. The configureRouter method will do the tricks for you. For instance:
export class ScreensList {
configureRouter(config, router) {
config.map([
{ route: 'screen/:id', moduleId: 'screens/screen/display', name: 'screen', title: 'Screen #1' }
]);
this.router = router;
}
}
Remember that ScreensList must be a screen of your router. It will not work if it is a custom element.
Take a look at the skeleton-navigation examples https://github.com/aurelia/skeleton-navigation. There are good examples, including child routing.

Meteor using alanning roles and aldeed collection2 together

I'm having an issue setting up roles in my project that uses meteor-collection2. I assume this is the roles package noted in the collection2 docs.
I'm using accounts-password and ian:accounts-ui-bootstrap-3 as my accounts solution. Here's my config for it:
Accounts.ui.config({
requestPermissions: {},
extraSignupFields: [{
fieldName: 'first-name',
fieldLabel: 'First name',
inputType: 'text',
visible: true,
validate: function(value, errorFunction) {
if (!value) {
errorFunction("Please write your first name");
return false;
} else {
return true;
}
}
}, {
fieldName: 'last-name',
fieldLabel: 'Last name',
inputType: 'text',
visible: true,
}, {
fieldName: 'terms',
fieldLabel: 'I accept the terms and conditions',
inputType: 'checkbox',
visible: true,
saveToProfile: false,
validate: function(value, errorFunction) {
if (value) {
return true;
} else {
errorFunction('You must accept the terms and conditions.');
return false;
}
}
}]
});
I added the roles field to my Users Schema:
Schemas.User = new SimpleSchema({
username: {
type: String,
// For accounts-password, either emails or username is required, but not both. It is OK to make this
// optional here because the accounts-password package does its own validation.
// Third-party login packages may not require either. Adjust this schema as necessary for your usage.
optional: true
},
emails: {
type: [Object],
optional: true
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
services: {
type: Object,
optional: true,
blackbox: true
},
profile: {
type: Object,
optional: true,
blackbox: true
},
"first-name": {
type: String
},
"last-name": {
type: String
},
// Add `roles` to your schema if you use the meteor-roles package.
// Option 1: Object type
// If you specify that type as Object, you must also specify the
// `Roles.GLOBAL_GROUP` group whenever you add a user to a role.
// Example:
// Roles.addUsersToRoles(userId, ["admin"], Roles.GLOBAL_GROUP);
// You can't mix and match adding with and without a group since
// you will fail validation in some cases.
roles: {
type: Object,
optional: true,
blackbox: true
}
});
And now I want to immediately create one user on the first time I run my project with an admin role and stop any others from being created afterwards:
/*----------------------------------------------- #2 Create admin user ----------------------------------------------*/
/*Notes: Create an admin-type user if no users exist yet.*/
if (Meteor.users.find().count() === 0) { /*------------------------------------ If there are no users created yet*/
var users = [{
username: "admin",
name: "admin",
email: "test#test.com",
roles: ['admin']
}];
_.each(users, function(user) {
var id = Accounts.createUser({
username: user.username,
email: user.email,
password: "mypassword123",
profile: {
name: user.name
},
first-name: Me,
last-name: MeName
});
if (user.roles.length > 0) {
// Need _id of existing user record so this call must come
// after `Accounts.createUser` or `Accounts.onCreate`
Roles.addUsersToRoles(id, user.roles);
}
});
}
/*-------------------------------------------------------------------------------------------------------------------*/
/*Prevent non-authorized users from creating new users*/
Accounts.validateNewUser(function(user) {
var loggedInUser = Meteor.user();
if (Roles.userIsInRole(loggedInUser, ['admin'])) {
return true;
}
throw new Meteor.Error(403, "Not authorized to create new users");
});
So far apparently so good: I get the new user.
The problem is when I use spacebars to hide admin features in html the created user isn't recognized as an admin and they are hidden from me...
{{#if isInRole 'admin'}}
<p>Exclusive to admin stuff</p>
{{/if}}
If using Roles as an Object (option #1) you must specify a group and permission for all users (I believe with Roles 2.0 which is coming out soon this will no longer be the case), so for something like an admin user you could use Roles.GLOBAL_GROUP which is used to apply blanket permissions across all groups.
For this, you would need to make the follow change:
Roles.addUsersToRoles(id, user.roles);
To this:
Roles.addUsersToRoles(id, user.roles, Roles.GLOBAL_GROUP);
You will also need to specify the group inside of your isInRole helper, here's an example of how that would look:
Roles.addUsersToRoles(joesUserId, ['manage-team'], 'manchester-united.com')
//manchester-united.com is the group
For your isInRole helper on the client, you would use this:
{{#if isInRole 'manage-team' 'manchester-united.com'}}
<h3>Manage Team things go here!</h3>
{{/if}}
You are currently using it as a String (Option #2, without groups). If you are planning on using groups for any users then you will need to make the changes I explained above (you can then remove option #2 as well), but if you don't plan on using groups for any users then you can remove Option #1 and simply use it as a String.
There is a helpful tutorial on the Roles package here, and the package docs are great too.

Durandal SPA on iPad goes to browser when navigating

Created ASP.Net SPA using Durandal. Added a forms login page that is hit prior to the SPA. When accessed on an iPad and saved to Home screen it opens like a native app, I can login and still stays in same window when it opens the SPA. BUT as soon as I navigate to another route it opens Safari instead.
Is there something I'm missing or any possible way I can avoid this, it would be nice if I can keep it looking like a native app.
EDIT:
As required logic from shell.js from Durandal viewmodels folder:
define(['plugins/router', 'durandal/app'], function (router, app) {
return {
router: router,
activate: function () {
router.map([
{ route: '', title:'Actions', moduleId: 'viewmodels/actions', nav: true },
{ route: 'welcome', title:'Welcome', moduleId: 'viewmodels/welcome', nav: true },
{ route: 'flickr', moduleId: 'viewmodels/flickr', nav: true },
{ route: 'clients', moduleId: 'viewmodels/clients', nav: true },
{ route: 'client/:id', moduleId: 'viewmodels/client', nav: false }
]).buildNavigationModel();
return router.activate();
},
attached: function () {
$("#logoff").show();
}
};
});
You can simply create a new Application in VS 2012+ using the downloadable Durandal SPA template from asp.net site.
Logic from shell.html:
<ul class="nav" data-bind="foreach: router.navigationModel">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }, html: title"></a>
</li>
</ul>

jstree 1.0 does not work well

I have a problem with the new version of jstree when using this part of code. At the first execution, the data function returns the root node. The problem is that this code never executes again. So whatever happens, I just have the root node. Does anybody know a solution?
$('#tree').jstree(
json_data: {
ajax: {
url: '<%=url %>',
dataType: "json",
data: function (n) {
return {
"id": n.attr ? n.attr("id") : 0
};
}
}
},
themes: { url: '/ThirdParty/jquery/jsTree/themes/', theme: "default", dots: true, icons: true },
plugins: ["json_data", "themes", "ui"]
})
{
As I understand it, jsTree renders the root node only, then when you open the root node, an ajax GET request to url is sent with the return value of the data function as a parameter string. For example:
http://example.com/my_url?id=42

Resources