How to use icon in routes using Durandal? - durandal

I was looking around the documentation of the structures of these route-info object, but I didn't find any. Since Durandal use other JS libs, I don't sure what is this belong to (maybe sammy?).
I am facing 2 problems:
Problem #1 I want to use an icon in the route information, and I found that I could use title or caption to accomplish that...
Ugly Option 1: using icon info in the caption
{ route: 'dashboard', title: 'Dashboard', moduleId: 'viewmodels/dashboard', nav: true, caption: 'icon-dashboard' },
and do some binding like this:
<i data-bind="attr: { 'class': caption}"></i>
<a data-bind="attr: { href: hash }, html: title"></a>
or Ugly Option2: using icon html code in the model
{ route: 'dashboard', title: 'Dashboard', moduleId: 'viewmodels/dashboard', nav: true, caption: '<i class="icon-plus-sign"></i> Dashboard' }
and the binding will be:
<a data-bind="attr: { href: hash }, html: caption"></a>
I personally like option 1, because the is separation of data and display. But the property (caption) is not the best place to put it... what other options are??? I saw people using setting, but again, what settings-options are?? can I create my own icon property?
Other Problem How to design sub-menu... if is there a property to reference a parent-route??
Update 8/23/2013 I found this info about Child Routers

You can add your own properties to the route object. This is the good thing about JavaScript!
So as you say, you could add a settings object to the route like this:
{ route: 'dashboard', title: 'Dashboard', moduleId: 'viewmodels/dashboard', nav: true, settings : { caption: 'icon-dashboard', another :'property'} }
And just do the binding the same way you were doing:
<i data-bind="attr: { 'class': settings.caption}"></i>
<a data-bind="attr: { href: hash }, text: title"></a>
Just make sure that all your objects that will be bound in the UI contain the settings.captionor add extra logic in the binding to manage route objects that which property isundefined.

Related

Vue js3: How to add router link & icons in aray items

I have an array of nav items including subnav I have added to a sidebar component in a dashboard I am making.
I will like to add router links with different icons to each item in the array. I am still trying to wrap my head around how to do it and I really need help.
below is the code sample.
Array nav items
state: {
navigation: [
{title: "Home"},
{
title: "Posts",
poen: true,
subnav:[
{title: "Published"},
{title: "Draft"},
{title: "Trashed"},
]
},
{
title: "Users",
open: true,
subnav:[
{title: "Admins"},
{title: "Authors"},
{title: "Editors"},
{title: "Subscribers"}
]
},
]
},
getters: {
navigation: state => {
return state.navigation
}
},
Sidebar menu items
<ul id="navigation">
<li v-for="(item, index) in navigation" :key="'item'+index">
<div class="title" #click="item.open = !item.open">{{item.title}}</div>
<Dropdown v-if="item.subnav" :list="item" />
</li>
Drop Down Items
<ul v-show="list.open">
<li v-for="(item, index) in list.subnav" :key="'item' +index">
<div class="title" #click="item.open = !item.open">{{item.title}}</div>
<Dropdown v-if="item.subnav" :list="item" />
</li>
So that in the end, I want to achieve something like this:
You can do it two ways (or more)
1st, you can import and add the icon class to the array of objects to later bind it to the template.
Eg. heroicons and usage.
import { UserCircleIcon, ..otherIconsImaginary } from "#heroicons/vue/solid";
navigation: [
{title: "Home", icon: UserCircleIcon},
{title: "Posts",poen: true,icon: PostsIcon,
subnav:[
{title: "Published", icon: SomeOtherIcon},
{title: "Draft", icon: DraftsIcon},
{title: "Trashed", icon: TrashedIcon},
]
}
]
Then on your template bind it as a component
2nd, Import your icons from anywhere and then define icon classes for all objects.
navigation: [
{ title: "Home", icon_class: "home-icon" }...
]
then bind it as an image like this
<img :src="homeIcon" alt="" v-if="item.icon_class === 'home-icon'" />
PS. I think you should use the state for user information management only. This code can be finished in layouts since you can have multiple layouts for authenticated, non-authenticated, 404, and other pages as per your business logic wideness.

Router-link dynamic vueJS

I'm new to vueJS. I am creating a news system to train myself. I'm a little problem. Here is the link that allows me to go to the detail of an article:
<router-link :to="{ name: 'Blog Details', params: { id: 1 }}"><img v-bind:src="postThumbnail" v-bind:alt="title"></router-link>
My component on which this link is located, has several props including the article id (actu_id).
In the link to the article, I would like the id located in the params not to be hard "1", but actu_id.
I do not know how to do.
When building things like this, first think how the data gets to your page. It's probably most efficient to use loops here, assuming you'll have multiple blog posts. Save them in the data in an array:
blogs: [
{
name: "Post 1",
id: 1,
thumbnail: "agsrgsghr.jpg",
},
{
name: "Post 2",
id: 2,
thumbnail: "agsrgsghr2.jpg",
},
],
And you can use template literals to set the router link parameters
<div v-for="blog in blogs" :key="blog.id">
<router-link :to="{ name: `${blog.name}`, params: { id: `${blog.id}` } }"
><img :src="blog.thumbnail" :alt="blog.name"
/></router-link>
</div>

Dynamic item template slots within v-data-table with custom components & helpers

Say I have a custom component that uses Vuetify's v-data-table within.
Within this component, there's multiple other custom components such as loaders and specific column-based components for displaying data in a certain way.
I found myself using the same code for filtering, retrieving data, loaders etc. across the project - so not very DRY.
The things that vary are:
API request url to retrieve data from (which I can pass to this generic component)
headers for v-data-table (which I pass to this generic component)
specific item slot templates!
(One file using this same code would need a column modification like the below, requiring different components sometimes too):
<template v-slot:[`item.FullName`]="{ item }">
<router-link class="black--text text-decoration-none" :to="'/users/' + item.Id">
<Avatar :string="item.FullName" />
</router-link>
</template>
Where another would have for example:
<template v-slot:[`item.serial`]="{ item }">
<copy-label :text="item.serial" />
</template>
There are many more unique "column templates" that I use obviously, this is just an example.
modifying items passed to v-data-table in a computed property (to add "actions" or run cleanups and/or modify content before displaying it - not related to actual HTML output, but value itself)
computed: {
items () {
if (!this.data || !this.data.Values) {
return []
}
return this.data.Values.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
hwVersion: this.$getItemHwVersion(item),
swVersion: this.$getItemSwVersion(item),
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
}
there are some unique methods that I can use on certain template slot item modifications, such as dateMoreThan24HoursAgo() below:
<template v-slot:[`item.LastLogin`]="{ item }">
<span v-if="dateMoreThan24HoursAgo(item.LastLogin)">{{ item.LastLogin | formatDate }}</span>
<span v-else>
{{ item.LastLogin | formatDateAgo }}
</span>
</template>
I can always make this global or provide them as a prop so this point should not be a big issue.
So my questions are:
What is the best way to use one component with v-data-table within but dynamically pass template slots and also allow item modification prior to passing the array to the v-data-table (as per point 3 and 4 above)
is there a better way to approach this since this seems too complex (should I just keep separate specific files)? It does not feel very DRY, that's why I'm not very fond of the current solution.
Basically I would be happy to have something like:
data: () => {
return {
apiPath: 'devices',
headers: [
{ text: 'Device', align: 'start', value: 'device', sortable: false, class: 'text-none' },
{ text: 'Serial Number', sortable: false, value: 'serial', class: 'text-none' },
{ text: 'Status', value: 'Status', class: 'text-none' },
{ text: 'Calibration', value: 'NextCalibrationDate', class: 'text-none' },
{ text: '', sortable: false, align: 'right', value: 'actions' }
],
itemsModify: (items) => {
return items.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
},
columnTemplatesPath: '/path/to/vue/file/with/templates'
}
}
And then I'd just call my dynamic component like so:
<GenericTable
:api-path="apiPath"
:headers="headers"
:items-modify="itemsModify"
:column-templates-path="columnTemplatesPath"
/>
Relevant but not exactly a solution to my question:
Is it possible to use dynamic scoped slots to override column values inside <v-data-table>?
Dynamically building a table using vuetifyJS data table

Durandal 2.0 - Child routers intended for nested menus?

I've building an app, and wanting to show a 2-tier menu, with both tiers always available. Durandal 2.0 introduced their new router, which supports 'child routers', which allow for easier deeplinking.
My question - Can I have my 'child' navigation routes permanently loaded (and a sub menu rendered when the parent is not active), or is the 'child router' design intended to lazy-load them to evaluate them once a deeplink is to be evaluated?
The Durandal examples show main navigation register a splat route, then when that view model is loaded/activated, the child router is registered.
E.g.: In the example provided with Durandal 2.0, the mian nev is registered in shell.js
router.map([
{ route: ['', 'home'], moduleId: 'hello/index', title: 'Validation test', nav: true },
{ route: 'knockout-samples*details', moduleId: 'ko/index', title: 'Knockout Samples', nav: true, hash: '#knockout-samples' }
]).buildNavigationModel()
.activate();
And the ko/index.js view model then registers the children (on activate())
var childRouter = router.createChildRouter()
.makeRelative({
moduleId:'ko',
fromParent:true
}).map([
{ route: '', moduleId: 'helloWorld/index', title: 'Hello World', type: 'intro' },
{ route: 'helloWorld', moduleId: 'helloWorld/index', title: 'Hello World', type: 'intro', nav: true},
{ route: 'clickCounter', moduleId: 'clickCounter/index', title: 'Click Counter', type: 'intro', nav: true}
]).buildNavigationModel();
Whereas I'm looking to define my routes in one place, e.g.:
var routes = [
{ route: ['', 'home'],
moduleId: 'hello/index',
title: 'Validation test',
nav: true },
{ route: 'knockout-samples*details',
moduleId: 'ko/index',
moduleRootId: 'ko', // Custom property to make child routes easier
title: 'Knockout Samples',
nav: true,
hash: '#knockout-samples',
childRoutes: [
{ route: '', moduleId: 'helloWorld/index', title: 'Hello World', type: 'intro' },
{ route: 'helloWorld', moduleId: 'helloWorld/index', title: 'Hello World', type: 'intro', nav: true},
{ route: 'clickCounter', moduleId: 'clickCounter/index', title: 'Click Counter', type: 'intro', nav: true}
]}
])
And the part I'm struggling with is registering the child routes, and have them evaulate correctly when navigating to them. (Rendering the menu would come later...)
// Load routes router.map(routes.navRoutes).buildNavigationModel().mapUnknownRoutes(function(instruction)
{
logger.logError('No Route Found', instruction.fragment, 'main', true);
});
// Try load child routes...
$.each(routes.navRoutes, function(index, parentRoute) {
if (!parentRoute.childRoutes) {
return;
}
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: parentRoute.moduleRootId,
fromParent: true // also tried false + non-splat routes...
}).map(parentRoute.childRoutes).buildNavigationModel();
});
I've encountered various errors in getting this nav to render. Mostly internal router.js errors when trying to calculate the hash (either from a non-activated parent, or the child) - so all hashes have been defined.
Once I've got it to map the navigation, the child routes don't seem to be accessible - they just load the main splat page without error.
So I'm wondering if I'm going about this the right way at all? (Registering all child routes up front, with the goal of being about to render a 2-tier menu).
I'm thinking the fallback is using something like the Durandal 1.2 answer about subrouting, using a combination of flat routing registration, custom 'isSameItem' function & computed observables to render the 2-tier navigation.
I do hope I can help You a bit.
Can You have 'child' routers permanently loaded?
As far I know You can't ( or rather there is not simple way to achieve that). Child routers are designed to provide sub-routing capability for particular view - You can think about them as a Durandal navigation inside Durandal view. If we check the example code on Durandal page we can easily see that child routing life-time is connected with given view. What is more if we check the code of function creating child routes we will see that it creates new router and only store reference to parent router - the parent router ( in most cases main router) does not have references to its childs
router.createChildRouter = function() {
var childRouter = createRouter();
childRouter.parent = router;
return childRouter;
};
What can be done to achieve multilevel routing in main router?
I had similar problem in the past but with old version of Durandal. For this problem I had started from scratch You gave and modifed it a bit - I get rid of splat route as its intend to use with child routes and my solution will not use them.
var routes = [
{
route: ['', 'home'],
moduleId: 'viewmodels/home',
title: 'Validation test',
nav: true
},
{
route: 'knockout-samples',
moduleId: 'viewmodels/ko/index',
moduleRootId: 'viewmodels/ko', // Custom property to make child routes easier
title: 'Knockout Samples',
nav: true,
hash: '#knockout-samples',
childRoutes: [
{ route: 'simpleList', moduleId: 'simpleList', title: 'SimpleList', nav: true, hash : 'simpleList' },
{ route: 'clickCounter', moduleId: 'clickCounter', title: 'Click Counter', nav: true, hash : 'clickCounter' }
]
}
];
Next step is converting this user-friendly definition to route table which can be easily register in main router.
$.each(routes, function(index, route) {
if (route.childRoutes === undefined)
return
$.each(route.childRoutes, function(index, childRoute) {
childRoute.route = route.route + '/' + childRoute.route;
childRoute.moduleId = route.moduleRootId + '/' + childRoute.moduleId;
childRoute.title = route.title + '/' + childRoute.title;
childRoute.hash = route.hash + '/' + childRoute.hash;
childRoute.parent = route.moduleRootId;
});
routes = routes.concat(route.childRoutes);
});
Last step is standard registering router and activating it.
return router.map(routes)
.buildNavigationModel()
.activate();
How to render register routing so multilevel layout is preserved?
My code works with Bootstrap 2.3.0 and render multilevel menu as dropdown button. When route has no child routes ( so is just simple route) and has no parent ( its 1st level navigation) is rendered as simple button. If route has child routes its rendered as dropdown button and child routes are added to dropdown list.
<div class="btn-group" data-bind="foreach: router.navigationModel">
<!-- ko if: $data.childRoutes === undefined && $data.parent === undefined -->
<a data-bind="css: { active: isActive }, attr: { href: hash }, text: title" class="btn btn-info" href="#"/>
<!-- /ko -->
<!-- ko if: $data.childRoutes !== undefined -->
<div class="btn-group btn-info">
<a data-bind="css: { active: isActive }, attr: { href: hash }, text: title" class="btn btn-info" href="#"/>
<button class="btn btn-info dropdown-toggle" data-toggle="dropdown">
<span class="caret"/>
</button>
<ul class="dropdown-menu" data-bind="foreach: childRoutes">
<a data-bind="css: { active: isActive }, attr: { href: hash }, text: title" class="btn btn-info" href="#"/>
</ul>
</div>
<!-- /ko -->
</div>
The styles probably need to be bit polished but overall logic is done.

Durandal 2.0 routing parameterized routes (same route different parameter)

I am probably missing something basic, but when building navigation I am attempting to define multiple parameterized routes in the shell. The idea is that all of these routes will pass the user through to the same view/vm, but the parameter will determine content that is displayed after an ajax call). The routing itself works well, but the title is always passed through from the first route in the list.
activate: function () {
router.makeRelative({moduleId: 'viewmodels'}).map([
{
route: 'grid/:page',
title: 'Title 1',
moduleId: 'grid',
nav: 3,
hash: '#grid/param1'
},
{
route: 'grid/:page',
title: 'Title 2',
moduleId: 'grid',
nav: 2,
hash: '#grid/param2'
},
{
route: 'grid/:page',
title: 'Title 3',
moduleId: 'grid',
nav: 4,
hash: '#grid/param3'
},
{
route: 'grid/:page',
title: 'Title 4',
moduleId: 'grid',
nav: 5,
hash: '#grid/param4'
}
]).buildNavigationModel();
};
So, regardless of which of the generated links a user clicks on, the Title is always returned as 'Title 1'. Nav order does not matter. The first physical object in the list will supply the title for all links. If I hard code the links into shell.html and use a splat in the router I do not have this problem, however, hard coding the links is not feasible nor desirable for the app.
So, the question is, what am I doing wrong?
In the code above there's truly only one route 'grid/:page'. By defining a parameterized route the router maps everything the match grid/:page to the first route.
See more about that in the router documentation http://durandaljs.com/documentation/Using-The-Router/.
Instead of using the router.navigationModel() build your own small navigation model.
Top level down approach:
Step 1 Defining a grid route with an optional parameter (/:page).
router
.makeRelative({moduleId: 'viewmodels'})
.map([
{
route: 'grid(/:page)',
title: 'grid page',
moduleId: 'grid',
hash: '#grid'
}
])
.buildNavigationModel();
Step 2 Navigation model
define(['plugins/router', 'knockout', './navItem'], function( router, ko, NavItem ) {
var ctor = function(){
this.childRouter = router;
this.param = ko.observable('');
this.navigation = ko.observableArray([
new NavItem({title: 'Title 1', param: 'param1'}),
new NavItem({title: 'Title 2', param: 'param2'}),
new NavItem({title: 'Title 3', param: 'param3'}),
new NavItem({title: 'Title 4', param: 'param4'})
]);
};
ctor.prototype.activate = function(param){
this.param('Param: ' + (param || 'no param!'));
};
return ctor;
});
Step 3 Navigation item model
define(['plugins/router'], function( router ) {
var ctor = function(options){
this._options = options || {};
this.init(this._options)
};
ctor.prototype.init = function(options){
this.title = options.title;
this.param = options.param;
this.hash = '#extras/optional/' + this.param;
};
ctor.prototype.isActive = function(){
return router.activeInstruction().fragment === this.hash.replace('#', '');
};
return ctor;
});
Step 4 Navigation view
<div class="navbar">
<div class="navbar-inner">
<ul class="nav" data-bind="foreach: navigation">
<li data-bind="css: { active: isActive() }">
<a data-bind="attr: { href: hash }, html: title"></a>
</li>
</ul>
<div class="loader pull-right" data-bind="css: { active: childRouter.isNavigating }">
<i class="icon-spinner icon-2x icon-spin"></i>
</div>
</div>
<div>
<h3 data-bind="text: param"></h3>
</div>
</div>
Live version can be seen at: http://dfiddle.github.io/dFiddle-2.0/#extras/optional.
Feel free to fork and adjust to your liking.