Durandaljs generating sub menu from json - json

Alright, I am fairly new to durandal. I am really struggling getting trying to accomplish this.
Here is what I am trying to do: There is a main navigation that is compromised of an inbox, draft, submitted, etc. Clicking on these, gives the user a submenu that comes out to the side of the main navigation. This submenu is generated by json data that I get from the server. Clicking an an option from the submenu should open the document in a viewer viewmodel based on the id of the document.
Ex.
User clicks on inbox
2. Menu comes out that has documents from their inbox. User clicks on view
3. Document that is clicked is displayed to the user.
4. So when they get to this point, I want the url to be mysite.com/#inbox/viewer/123456 (123456 is documentid)
I just haven't been able to find decent examples that are similar to this, and was wondering if someone could help point me in the right direction.
I kind of did it by making each main navigation link to a module, and have a document window in each of those modules, but I thought there had to be a better way. So what I am trying to do is keep my subnavigation in the shell. I don't want to have a module for each of my main navigation items.
Here is my shell code right now:
shell.js
define(['durandal/system', 'services/logger', 'plugins/router', 'durandal/activator'], function (system, logger, router, activator) {
//#region Internal Methods
function log(msg, data, showToast) {
logger.log(msg, data, system.getModuleId(shell), showToast);
}
function logError(msg, data, showToast) {
logger.logError(msg, data, system.getModuleId(shell), showToast);
}
function navigateRoute(hashValue) {
var target = hashValue.hash;
$("body").addClass("subnav-active");
document.cookie = "subNav=true";
$(target).addClass("current");
router.navigate(target, {replace: true, trigger: false });
}
var routes = [
{ route: '', hash: '#home', moduleId: 'home', title: '', nav: false, cssClass: 'icon-inbox' },
{ route: 'inbox', hash: '#inbox', moduleId: 'inbox', title: 'Inbox', nav: true, cssClass: 'icon-inbox' },
{ route: 'drafts', hash: '#drafts', moduleId: 'drafts', title: 'Drafts', nav: true, cssClass: 'icon-file-alt' },
{ route: 'submitted', hash: '#submitted', moduleId: 'submitted', title: 'Submitted', nav: true, cssClass: 'icon-hand-right' },
{ route: 'completed', hash: '#completed', moduleId: 'completed', title: 'Completed', nav: true, cssClass: 'icon-check' },
{ route: 'settings', hash: '#settings', moduleId: 'settings', title: 'Settings', nav: true, cssClass: 'icon-cog' }
];
//#endregion
var shell = {
activate: function () {
router.on('router:route:not-found', function (fragment) {
logError('No Route Found', fragment, true);
});
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
}
,
router: router,
navigateRoute: navigateRoute
};
return shell;
});
shell.html
<div class="main-wrapper wrapper">
<div class="container_template">
<header class="pageheader">
<nav class="mobile-nav">
<a class="menu-button" href="#main-navigation">
<i class="icon-reorder"></i><span>Menu</span>
</a>
</nav>
<h1 class="logo">template</h1>
<nav class="nav-user">
<a class="close" href="#">
<i class="icon-chevron-right"></i>
<span>Main</span>
</a>
</nav>
</header>
<!-- Begin Header/Navigation -->
<div class="main-navigation" role="banner">
<div class="main-navigation-inner inner">
<nav class="navigation">
<ul data-bind="foreach: router.navigationModel">
<li>
<a class="nav-button" data-bind="attr: {'data-target': hash,}, css: {'nav-button' : isActive, active: isActive }, click: function(hash) { $root.navigateRoute(hash);return true},">
<i data-bind="css: cssClass"></i>
<span data-bind="html: title"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- End Header/Navigation -->
<!-- Sub Navigation Elements -->
<!--This is my sbumenu-->
<div id="inbox" class="navigation-sub">
<div class="navigation-sub-inner inner">
<div class="navigation-sub-header">
<a class="close" href="#">×</a>
<h3>Inbox</h3>
</div>
<div class="navigation-sub-search">
<form>
<input type="text" placeholder="Search" />
<button>Go</button>
</form>
</div>
<ul data-bind="contentsOfInbox">
<li>
<a data-bind="href : linktoDocuemntViewer" href="" class="form-open">
<i class="icon-file-alt"></i><span data-bind="html: NameofDocumentHere"></span>
<span class="date" data-bind="html: DateOfDocumentHere"></span>
</a>
</li>
</ul>
</div>
</div>
<!-- End SUB NAV -->
<!--Begin Main-->
<!--Documents would appear here-->
<div class="main" data-bind=" router: { cacheViews: false }">
</div>
<!-- End Main -->
</div>
Thanks for any help

For what you are trying to do it sounds like using the navigationModel will not work. From my experience, the navigationModel and route are a 1 to 1 relationship. What you are trying to do is have one route with parameters tied to multiple menu items. To do this you should separate your menus and routes. Remember at the heart of everything you are just binding javascript objects and collections to html elements. The router.navigationModel property is just that, a collection of javascript objects that is built from your defined routes. When you call .buildNavigationModel() that is what it is doing, building up a new collection of javascript objects for you to bind to your menu html. There is nothing stopping you from creating a completely new property on your shell viewmodel that contains your own custom collection of javascript menu objects and binding that to your UL/LI navigation html. If you make this new menu collection a computedObservable you could then add to it in a lazy fashion as you needed to and since it is bound with knockout, changes would appear in the UI automatically. I have done this on a number of different projects and it works fine. I build a menu table in the backend and then just return only the items that the user has access to and use that to build my UI menu. To make navigation work, I keep the urlhash in the table and bind that to the anchor tags. When you are creating routes, you don't have to set the hash property, the router plugin will take care of that for you. So for your requested example, create a menu collection like this:
{ displayname: 'Inbox' cssClass: 'icon-inbox', urlHash: '' children[
{ displayname: 'Doc 1' cssClass: 'icon-message', urlHash: 'inbox/viewer/12345' },
{ displayname: 'Doc 2' cssClass: 'icon-message', urlHash: 'inbox/viewer/12346' },
{ displayname: 'File 1' cssClass: 'icon-message', urlHash: 'inbox/viewer/12347' }]}
create a route like this:
{ route: 'inbox/viewer/:messageId', moduleId: 'home', title: '', nav: false, cssClass: 'icon-inbox' },
see the durandal docs for info on passing parameters to routes
expose this menu as a computedObesrvable from your shell and bind that to you UL/LI menu html instead of the router.NavigationModel. When needed, change the contents of the children property of the inbox menu with new data from the server.

Related

Scrolling to a div inside a container with overflow-y in Angular

In an angular app I have two bootstrap columns - the left col contains a scrollable div with overflow-y: auto; - the right col is a set of links that are essentially bookmarks that jump to the sections within the left scrollable container.
As far as I can see I'm facing two challenges:
1 - Setting up Angular to recognise I wish to scroll to page section when routing is enabled.
This I've over come by using router options and fragments
const routerOptions: ExtraOptions = {
useHash: false,
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 64]
};
imports: [
...
RouterModule.forRoot(appRoutes, routerOptions),
...
]
And the link:
<a [routerLink]='"."' fragment="uniquedivid">Link</a>
2 - Getting a fragment / anchor to be scrolled into place when it's inside an overflow
From what I've read on SO and other blogs I can see a varying array of ways to do this but most of them use JQuery (which I want to avoid as I'm using pure Angular) and none of these methods seem to compliment the route system in Angular.
I don't know if this makes a difference but my links come AFTER my overflow-ed div as shown below:
<div class="row">
<div class="col">
<div id="overflow">
<div id="section-1"></div>
<div id="section-2"></div>
<div id="section-3"></div>
</div>
</div>
<div class="col">
<a [routerLink]='"."' fragment="section-1">Section 1</a>
<a [routerLink]='"."' fragment="section-2">Section 1</a>
<a [routerLink]='"."' fragment="section-3">Section 3</a>
</div>
</div>
I feel like I'm along the right lines here there's just something I'm missing, or maybe I'm overcomplicating things and there's a simpler way I'm not aware of?
I'm not sure you got a solution for it, but if I were you, I would have used a well organized npm module that can directly help you organize the breadcrumbs. If I understand the question correctly, you can use angular-crumbs.
You can organize your routing like this :
export const rootRouterConfig: Routes = [
{path: '', redirectTo: 'home', pathMatch: 'full'},
{path: 'home', ..., data: { breadcrumb: 'Home'}},
{path: 'about', ..., data: { breadcrumb: 'About'}},
{path: 'github', ..., data: { breadcrumb: 'GitHub'},
children: [
{path: '', ...},
{path: ':org', ..., data: { breadcrumb: 'Repo List'},
children: [
{path: '', ...},
{path: ':repo', ..., data: { breadcrumb: 'Repo'}}
]
}]
}
];
Then try to add the breadcrumb selector in the component where your routes are destined to (when using bootstrap):
<breadcrumb #parent>
<ol class="breadcrumb">
<ng-template ngFor let-route [ngForOf]="parent.breadcrumbs">
<li *ngIf="!route.terminal" class="breadcrumb-item">
{{ route.displayName }}
</li>
<li *ngIf="route.terminal" class="breadcrumb-item active" aria-current="page">{{ route.displayName }}</li>
</ng-template>
</ol>
</breadcrumb>
There are other ways too, you can check here to implement.

Creating a dynamic Vue list that can be updated via custom props in HTML

New to VueJS. I am trying to build a custom ul component for a webpage that can be populated and updated via custom props (preferably string, but doesn't have to be), specifically in the HTML so that any other dev can simply use/update/add to the custom component with said prop, and it will add a new li through the addition of a second, third, fourth, etc. prop, appending the previous li. I am also struggling to see if more than one input type can be used on a custom prop. For a better explanation heres a coded example of what I currently have and what I would like to do:
Vue.component('resources', {
template: `
<!-- Resources Component -->
<div class="resources">
<div class="heading">
<p>Resources</p>
</div>
<ul class="resource-list">
<li v-for="item in items">
<a :src="item[source]">{{ item.message }}</a>
</li>
</ul>
</div>
`,
props: {
source: {
type: String,
default: "."
},
message: {
type: String
}
},
data () {
return {
items: [
{
message: {
type: String
},
source: {
type: String,
default: "."
}
}
]
}
}
});
And in my HTML the component looks like this:
<helpful-resources
message="test"
source="."
></helpful-resources>
This 1000% has a lot of issues, but ideally I would like to have something along the lines of this:
<helpful-resources
item: src="example url 1" message="test message 1"
item: src="example url 2" message="test message 2"
></helpful-resources>
With every addition of a new 'item' appending the previous list item with a new one with the ability to change the src and the message over and over again as needed for however many items are needed in the list.
Any help/clarification would be greatly appreciated. Thanks!
In the parent component:
<template>
<div class="resources">
<div class="heading">
<p>Resources</p>
</div>
<Helpful-resources :listItems="listItems"></Helpful-resources>
</div>
</template>
<script>
#import HelpfulResources from '#/path/to/HelpfulResources';
export default {
name: 'Resource',
components: {
HelpfulResources
},
data() {
return {
listItems: [
{src: 'link to item', message: 'special message'},
{src: 'link to item2', message: 'special message2'},
// More items ...
]
}
}
}
</script>
<style lang="scss">
/* styles */
</style>
Your component could be structured like this:
Helpful-resources.vue
<template>
<ul class="resource-list">
<li v-for="(item, index) in listItems" :key="'listItem-'+index">
<a :href="item.src">{{ item.message }}</a>
</li>
</ul>
</template>
<script>
export default {
name: 'helpful-resource',
props: [ 'listItems'],
data() {
return {
// More data ...
}
}
}
</script>
<style lang="scss">
/* styles */
</style>
Note this is styled in the vue-cli fashion, but you can modify it to fit your needs.
EDIT
To include it within an html file you would place your Vue components within the body, script tags just below the body tag.
<div id="app">
<resources :source="someData" :message="message" id="r"></resources>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<script>
let resources = Vue.component('resources', {
template: `<div class="resources">
<div class="heading"><p>Resources</p></div>
<ul class="resource-list">
<li v-for="(item, index) in items" :key="index"><a :href="source">{{ item.message }}</a></li>
</ul>
</div>`,
props: {
number: Number,
source: {
type: String,
default: "."
},
message: {
type: String,
default: 'No message'
},
// Example of multiple data types
propB: [String, Number]
},
data() {
return {
items: [
{
message: this.message,
source: this.source
}
]
}
}
});
new Vue({
el: '#app',
components: {
resources
},
data: {
someData: 'path/to/source',
message: 'Special Message'
},
});
</script>
Here's a link to the fiddle anyways...Fiddle
As far as updating the list goes, you could use an API call to get data asynchronously or allow users to add info via button or input and use a method. Or if you are talking strictly hardcoding extra values, other developers would add to your file...
Hopefully this helps. If not, please clarify.

VueJS - calling multiple image src as custom props

I have a small set of icons i want to call as a custom image prop depending on what type of item the component is. Code looks like this:
Vue.component('otherArticles', {
template: `
<!-- Component -->
<li>
<img :src="icon.text && icon.video" alt="icon">
<a :href="url">{{ Title }}</a>
</li>
`,
props: {
title: String,
url: String,
icon: [
{
text: "/resources/img/icons/text-icon.svg",
video: "/resources/img/icons/video-icon.svg"
}
]
}
});
Ideally in my html I would like to call them like this:
<!--Component with text icon-->
<other-articles
icon='text' <!-- how i'd like to call the text icon as img src -->
url="."
title="Text article title">
</other-articles>
<!--Component with video icon-->
<other-articles
icon='video' <!-- how i'd like to call the video icon as img src -->
url="."
title="Video article title">
</other-articles>
The img src binding is incorrect I know, i'm using it as an example of how i'm thinking it should be done, but I'm looking for any and all recommendations on how to do this correctly so I can call it the html as the example shows.
I only have these two icons and the src location for each may change but i would like to call it the same way even if i have to update the src location for either one in the future, keeping the html calls the same or similar. Any help would be appreciated. Thanks
First start by declaring your icon list as the following in your data function:
data() {
return {
iconList: {
text: '/resources/text.png',
video: '/resource/video.png',
}
};
}
Make sure to remove the list and rename the object, as you cannot have a prop and an entry in data with the same name. Then add your definition for icon to your props section as the following:
props: {
icon: {
type: String,
required: true,
},
},
This tells Vue to typecheck the prop as a string, and warn when it's not present or not a string.
Now you need to update your template function to use this new prop as an key to lookup the related icon:
template: `
<img :src="iconList[icon]"/>
`,
Now you can use your component as <comp icon="video"/>

How to trigger multiple outlets with one routerLink in angular2 (2.1.1)

I'd like to swap out components in 2 separate areas of the DOM when I select a routerLink element. How can I route a single routerLink to 2 <router-outlet>s and designate a unique component for each <router-outlet>?
I'd like something like this:
<div id="region1>
<a routerLink="/view1" routerLinkActive="active">View 1</a>
<a routerLink="/view2" routerLinkActive="active">View 2</a>
<!-- First area to swap -->
<router-outlet name="sidebar"></router-outlet>
<div>
<div id="region2>
<!-- Second area to swap -->
<router-outlet name="mainArea"></router-outlet>
<div>
routes
const routes: Routes = [
{ path: '', redirectTo: 'view1', pathMatch: 'full'},
{ path: 'view1', {
outlets :
[
//one path specifies 2 components directed at 2 `router-outlets`
component: View1Sidebar, outlet : 'sidebar'
component: View1mainArea, outlet : 'mainArea'
]
}
},
{ path: 'view2', {
outlets :
[
component: View2Sidebar, outlet : 'sidebar'
component: View2mainArea, outlet : 'mainArea'
]
}
},
];
This cannot be done exactly as you ask. The purpose of a router is to maintain information about the specific page.
If you want to show and hide any components without reflecting any route information then you'll want to use the *ngIf directive. To use it like this, you'll need to keep a variable in you application somewhere that can be used to trigger the *ngIf directive.
You can make use any type of data, but you need to pass it to the *ngIf statement in the form of a boolean or expression that resolves to a boolean: here are examples"
component
showComponentBool: boolean = true;
showComponentStr: string = 'show';
html
<div *ngIf="showComponentBool">
<div *ngIf="showComponentStr='show'"></div>
</div>
With "#angular/core": "^4.0.0"
<a [routerLink]="[{ outlets: { primary: 'contact', aux: 'aside' }}]">Contact + Aux</a>
https://stackoverflow.com/a/42558766/2536623

Multiple UI-Views not working as expected

I am trying to use multiple UI-views in my AngularJS app and it is not working as expected. The app is a dashboard.
My directory structure is as follows:
index.html
app/
app.js
dashboard/
dashboard.html
dashboard.js
partials/
business.html
items.html
orders.html
sales.html
login/
login.html
login.js
The problem that I am having is that my index.html file has the following line of code:
<div ui-view></div>
The above line enables my application to show the login.html page and dashboard.html page. Now I want to be able to have partial views in my dashboard.html page and so I have also put the same line of code
<div ui-view></div>
in order to be able to embed partial views in my dashboard page. Instead of embedding though, the page instead just redirects. So for example if I am in my dashboard.html and click on a navigation item such as 'business', I am redirected to partials/business.html instead of the content being embedded.
1) Can I have multiple ui-views embedded within each other?
2) Am I correctly embedding the partial views?
I have scoured the internet but cannot find a solution. Thanks in advance for the help!
You can definitely have multiple embedded views.
Check out these AngularJS UI-Router tutorials: Nested Views and Multiple Named Views.
Let me know if you still have issues after looking them over.
You can define a ui-view inside another ui-view. I have implemented it in the following manner and its pretty straight forward.
Inside index.html I have code:
<div ui-view=""></div>
Then inside user.html I have code
<div ui-view=""></div>
And I have defined a config for displaying my views as
.config(function($urlRouterProvider, $stateProvider) {
var users = {
//Name must be in format Parent.Child
name: 'users',
url: '/user',
templateUrl: 'users/user.html',
controller: 'usersHandler',
data: {
pageTitle: 'Welcome to Users'
},
},
createUsers = {
name: 'users.createUsers',
url: '/createUser',
templateUrl: 'users/createUser.html',
data: {
pageTitle: 'Create Users'
}
},
listUsers = {
name: 'users.listUsers',
url: '/listUsers',
templateUrl: 'users/userLists.html',
data: {
pageTitle: 'Users listing'
}
},
getUserDealer = {
name: 'users.getUserDealer',
url: '/getUserDealer',
templateUrl: 'users/getUserDealer.html',
data: {
pageTitle: 'Users dealer listing'
}
},
editUser = {
name: 'users.editUser',
url: '/editUser',
templateUrl: 'users/editUser.html',
data: {
pageTitle: 'Edit User'
}
};
//Similarly define all the combination you want to separate
//add routes to stateProvider
$stateProvider
.state('users', users)
.state('users.createUsers', createUsers)
.state('users.listUsers', listUsers)
.state('users.getUserDealer', getUserDealer)
.state('users.editUser', editUser);
$urlRouterProvider.otherwise('/user/listUsers');
});
Whats happening is this that user.html is my parent file which is loaded inside index.html and editUser.html, getUserDealer.html and userLists.html etc are its children which I load within user.html using ui-view.
And I provide the links for nested pages as:
<ul class="nav navbar-nav">
<li>NEW USER</li>
<li>GET USER</li>
</ul>
This can be extended to additional parents and their children as per the need.
Hope it helps!!