AngularJS ui-sref external (absolute) link - javascript

I wrote a little angular app. I've got an array of menu items which I print in my template:
<nav id="menu">
<ul>
<li ng-repeat="i in menuItems"
ui-sref="{{ i | removeSpacesThenLowercase }}"
ui-sref-active="active">{{ i }}</li>
</ul>
</nav>
And in my app.js I declared my states using ui-router like:
.state('camera', {
url: '/selection',
templateUrl: '/views/selection.html',
uiShade: 'light back',
back: 'intro'
})
Internal URLs work just fine, but what if I want to do this?
.state('facebook', {
url: 'https://www.facebook.com/'
})
This obviously doesn't work. What would be the best approach to have some external (absolute) links in my template without having two separate arrays?

Ui-sref refers to a state. Your views are states. Externals sites aren't states, it's just some outside links.
I suggest you to refactor your menu generator to handle different type of menu entries :
state based link (link generated through ui-sref)
standard link (link generated through href, for external links, emails, etc)
Then you just have to populate menuItems with an array of different objects

I fixed this in my application using ng-if.
Example menu items:
$scope.navItems = [{
id: 1,
title: 'Internal Link',
href: null,
sref: 'internal-state'
},
{
id: 2,
title: 'External Link',
href: 'https:/google.co.uk',
sref: null
}];
Then in the HTML I set the ng-repeat on the <li> but include an <a> for href and one for sref, each with an ng-if.
<li ng-repeat="item in navItems">
<a ng-if="item.sref" ui-sref="{{item.sref}}">{{ item.title }}</a>
<a ng-if="item.href" href="{{item.href}}">{{ item.title }}</a>
</li>

I fixed this by creating a second array for the external links and an ng-click function.
$scope.elink = function(element) {
if (confirm("You're leaving the app, are you sure?")) {
window.location = element;
}
};

Related

Maping inside a map - Vanilla Javascript

Coding noob here :)
I'm working on a project where I have prepared an array that I'm mapping over and printing. Inside the array I have another array, called "lang", that I can't reach from the map. I want to print each item in that array into an <li>, as the code is now all the items are printed as one <li> , which I don't want. I have tried maping inside the map in various ways but not gotten it to work.
Is the right way to do a map inside the map? In that case, how is it done?
Is there another way to do this?
Please see part of the array and the map function I have created down below. projectContainer, which I'm adding the innerHTML to, is a div inside the index.html file.
},
{
img: "./pictures/guess-who.png",
projectTitle: 'GUESS WHO?',
projectDescription: 'Recreation of the game "Guess who?" built with HTML, CSS and JavaScript.',
lang: [
'HTML', 'CSS', 'JavaScript'
],
url: '',
className: '',
id: 6
}
]
}
const printProjects = () => {
projectArray.projects.map((project) => {
projectContainer.innerHTML += `
<div class="project-container ${project.id}">
<img src=${project.img} alt="picture of guess who game"></img>
<h4>${project.projectTitle}</h4>
<p>${project.projectDescription}</p>
<ul>
<li>${project.lang} </li>
</ul
</div>
`
})
}
printProjects()
You can do something like this and use map(...).join('')
<ul>
${project.lang.map(el => `
<li>${el}</li>
`).join('')}
</ul>

Vue Router Setup work when visited directly via URL

I'm looking for advice on how to go about this routing scenario:
I have the following HTML that loops category and items in the category. The <router-view> is inside the category so that when an item is clicked it will open only in the category that related to that item.
<ul>
<li v-for="cat in categories">
<ul>
<li v-for="business in filteredByCat">{{business.name}}</li>
</ul>
<router-view v-if="..."></router-view>
</li>
</ul>
My routes are as follows:
{
path: '/businesses',
name: 'Directory',
component: Directory,
children: [{
path: ':listing',
name: 'Listing',
component: Listing
}]
}
Visualization:
How do I get the data of the item clicked to pass to the router-view? I assume I'd use props but that wouldn't work if the user visited details directly by URL?
I tried getting the item like so:
methods: {
finalItem ($route) {
var match = this.businesses.filter((business) => {
return business.link === $route.params.listing
})
return match[0]
}
}
This doesn't work, even if it did, this feels wrong. Is there a way to pass the data in a way that would preserve even when visited directly? This is my primary concern. (I understand the repeated <router-view> is bad code but am not sure how to get around doing that with my layout. Open to suggestions on that too though.)
The way you're using router-view, you might as well just drop a component in. As far as using multiple router-views goes, it's very common, so idk what #varbrad is talking about there. Child routes are fine.
The not-so-common part is using multiple router-view's in one component. The UI you're aiming for is nearly identical to Netflix. If you check out what they're doing, you'll see that they pass a movie ID (business id/shortname) as "jbv" and a row number (category name) as "jbr" in the route query.
Let's mimic this in your component:
I'm not sure what filteredByCat looks like, but the way you have it set up, it would list the same businesses for every category. Try something like this:
computed:{
businessesByCategory(){
let dictionary = {};
this.businesses.forEach(business=>{
business.categories.forEach(category=>{ // assuming each business has an array of categories
dictionary[category] = dictionary[category] || [];
dictionary[category].push(business)
})
})
return dictionary;
}
},
data(){
return {
activeBusiness:null
}
},
methods:{
setActiveBusiness(business){
this.activeBusiness = business;
},
setUrlQuery(listing, category){
this.$route.query.listing = listing;
this.$route.query.category = category;
},
openListing(business, category){
this.setActiveBusiness(business);
this.setUrlQuery(business.link, category);
}
}
-
<ul>
<li v-for="cat in categories">
<ul>
<li
v-for="business in businessesByCategory[cat]"
#click="openListing(business, cat)"
>
{{business.name}}
</li>
</ul>
<Listing :business="activeBusiness" v-if="$route.query.category == cat"></Listing>
</li>
</ul>

Angularjs when middle mouse button clicked scope dispersed

Im using ui-sref for changing state on dom. When user clicked on this reference scope is destroying what is the best practise about this case. I dont want to store current scope data in some array or something like that. Here is my code;
<td class="v-middle">
<a ui-sref="app.customer-profile-details({id: '{{entry.profileId ? entry.profileId:0}}',
operation:'update'})">
<span class="text-primary">
<strong><u>{{entry.last_interaction | date:"MM/dd/yyyy h:mm"}}</u>
</strong></span>
</a>
</td>
.state('app.customer-profile-details', {
url: '/customer-profile-details',
templateUrl: 'views/customer-profiles/customer-profile-details.html',
controller: 'CustomerProfileDetailsController',
data: {
title: 'customer-profile-details.TITLE',
icon: 'icon-user',
link: null,
back: 'app.customer-profiles'
},
params: {id: null, operation: null},
resolve: load([])
The code of my state of above. Thank you.
I suggest you store the state on a service instead of on the scope

How to make Automated Dynamic Breadcrumbs with AngularJS + Angular UI Router

One key component to web applications is breadcrumbs/navigation. With Angular UI Router, it would make sense to put the breadcrumb metadata with the individual states, rather than in your controllers. Manually creating the breadcrumbs object for each controller where it's needed is a straight-forward task, but it's also a very messy one.
I have seen some solutions for automated Breadcrumbs with Angular, but to be honest, they are rather primitive. Some states, like dialog boxes or side panels should not update the breadcrumbs, but with current addons to angular, there is no way to express that.
Another problem is that titles of breadcrumbs are not static. For example, if you go to a User Detail page, the breadcrumb title should probably be the user's Full Name, and not a generic "User Detail".
The last problem that needs to be solved is using all of the correct state parameter values for parent links. For example, if you're looking at a User detail page from a Company, obviously you'll want to know that the parent state requires a :companyId.
Are there any addons to angular that provide this level of breadcrumbs support? If not, what is the best way to go about it? I don't want to clutter up my controllers - I will have a lot of them - and I want to make it as automated and painless as possible.
Thanks!
I did solve this myself awhile back, because nothing was available. I decided to not use the data object, because we don't actually want our breadcrumb titles to be inherited by children. Sometimes there are modal dialogs and right panels that slide in that are technically "children views", but they shouldn't affect the breadcrumb. By using a breadcrumb object instead, we can avoid the automatic inheritance.
For the actual title property, I am using $interpolate. We can combine our breadcrumb data with the resolve scope without having to do resolves in a different place. In all of the cases I had, I just wanted to use the resolve scope anyway, so this works very well.
My solution also handles i18n too.
$stateProvider
.state('courses', {
url: '/courses',
template: Templates.viewsContainer(),
controller: function(Translation) {
Translation.load('courses');
},
breadcrumb: {
title: 'COURSES.TITLE'
}
})
.state('courses.list', {
url: "/list",
templateUrl: 'app/courses/courses.list.html',
resolve: {
coursesData: function(Model) {
return Model.getAll('/courses');
}
},
controller: 'CoursesController'
})
// this child is just a slide-out view to add/edit the selected course.
// It should not add to the breadcrumb - it's technically the same screen.
.state('courses.list.edit', {
url: "/:courseId/edit",
templateUrl: 'app/courses/courses.list.edit.html',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne("/courses", $stateParams.courseId);
}
},
controller: 'CourseFormController'
})
// this is a brand new screen, so it should change the breadcrumb
.state('courses.detail', {
url: '/:courseId',
templateUrl: 'app/courses/courses.detail.html',
controller: 'CourseDetailController',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne('/courses', $stateParams.courseId);
}
},
breadcrumb: {
title: '{{course.name}}'
}
})
// lots more screens.
I didn't want to tie the breadcrumbs to a directive, because I thought there might be multiple ways of showing the breadcrumb visually in my application. So, I put it into a service:
.factory("Breadcrumbs", function($state, $translate, $interpolate) {
var list = [], title;
function getProperty(object, path) {
function index(obj, i) {
return obj[i];
}
return path.split('.').reduce(index, object);
}
function addBreadcrumb(title, state) {
list.push({
title: title,
state: state
});
}
function generateBreadcrumbs(state) {
if(angular.isDefined(state.parent)) {
generateBreadcrumbs(state.parent);
}
if(angular.isDefined(state.breadcrumb)) {
if(angular.isDefined(state.breadcrumb.title)) {
addBreadcrumb($interpolate(state.breadcrumb.title)(state.locals.globals), state.name);
}
}
}
function appendTitle(translation, index) {
var title = translation;
if(index < list.length - 1) {
title += ' > ';
}
return title;
}
function generateTitle() {
title = '';
angular.forEach(list, function(breadcrumb, index) {
$translate(breadcrumb.title).then(
function(translation) {
title += appendTitle(translation, index);
}, function(translation) {
title += appendTitle(translation, index);
}
);
});
}
return {
generate: function() {
list = [];
generateBreadcrumbs($state.$current);
generateTitle();
},
title: function() {
return title;
},
list: function() {
return list;
}
};
})
The actual breadcrumb directive then becomes very simple:
.directive("breadcrumbs", function() {
return {
restrict: 'E',
replace: true,
priority: 100,
templateUrl: 'common/directives/breadcrumbs/breadcrumbs.html'
};
});
And the template:
<h2 translate-cloak>
<ul class="breadcrumbs">
<li ng-repeat="breadcrumb in Breadcrumbs.list()">
<a ng-if="breadcrumb.state && !$last" ui-sref="{{breadcrumb.state}}">{{breadcrumb.title | translate}}</a>
<span class="active" ng-show="$last">{{breadcrumb.title | translate}}</span>
<span ng-hide="$last" class="divider"></span>
</li>
</ul>
</h2>
From the screenshot here, you can see it works perfectly in both the navigation:
As well as the html <title> tag:
PS to Angular UI Team: Please add something like this out of the box!
I'd like to share my solution to this. It has the advantage of not requiring anything to be injected into your controllers, and supports named breadcrumb labels, as well as using resolve: functions to name your breadcrumbs.
Example state config:
$stateProvider
.state('home', {
url: '/',
...
data: {
displayName: 'Home'
}
})
.state('home.usersList', {
url: 'users/',
...
data: {
displayName: 'Users'
}
})
.state('home.userList.detail', {
url: ':id',
...
data: {
displayName: '{{ user.name | uppercase }}'
}
resolve: {
user : function($stateParams, userService) {
return userService.getUser($stateParams.id);
}
}
})
Then you need to specify the location of the breadcrumb label (displayname) in an attribute on the directive:
<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>
In this way, the directive will know to look at the value of $state.$current.data.displayName to find the text to use.
$interpolate-able breadcrumb names
Notice that in the last state (home.userList.detail), the displayName uses the usual Angular interpolation syntax {{ value }}. This allows you to reference any values defined in the resolve object in the state config. Typically this would be used to get data from the server, as in the example above of the user name. Note that, since this is just a regular Angular string, you can include any type of valid Angular expression in the displayName field - as in the above example where we are applying a filter to it.
Demo
Here is a working demo on Plunker: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview
Code
I thought it was a bit much to put all the code here, so here it is on GitHub: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs
I made a Angular module which generate a breadcrumb based on ui-router's states. All the features you speak about are included (I recently add the possibility to ignore a state in the breadcrumb while reading this post :-) ) :
Here is the github repo
It allows dynamic labels interpolated against the controller scope (the "deepest" in case of nested/multiple views).
The chain of states is customizable by state options (See API reference)
The module comes with pre-defined templates and allows user-defined templates.
I do not believe there is built in functionality, but all the tools are there for you, take a look at the LocationProvider. You could simply have navigation elements use this and whatever else you want to know just inject it.
Documentation
After digging deep into the internals of ui-router I understood how I could create a breadcrumb using resolved resources.
Here is a plunker to my directive.
NOTE: I couldn't get this code to work properly within the plunker, but the directive works in my project. routes.js is provided merely for example of how to you can set titles for your breadcrumbs.
Thanks for the solution provided by #egervari. For those who need add some $stateParams properties into custom data of breadcrumbs. I've extended the syntax {:id} for the value of key 'title'.
.state('courses.detail', {
url: '/:courseId',
templateUrl: 'app/courses/courses.detail.html',
controller: 'CourseDetailController',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne('/courses', $stateParams.courseId);
}
},
breadcrumb: {
title: 'course {:courseId}'
}
})
Here is an Plunker example. FYI.

Add and remove templates

Lets say I have this 'Model'
var lists = [
{
title: 'Default title',
image: '/img/default.jpeg',
section: [
{ name: 'Default Name',
description: 'Default Description'
}
],
activity: ['default']
}
];
Here is the view templates
<template name="main_list_view">
{{#each list.section}}
{{> section}}
{{/each}}
<a id="addSection" href='#'>Add Section</a>
</template>
<template name="section">
<li>{{section.name}}</li>
<li>{{section.description}}</li>
<a class="deleteSection" href='#'>Delete Section</a>
</template>
I need help w the logic for add more sections and remove a specific section from the colection.
I have checked mongoDB and looks like I have to use something like addToset and $unset for update the model, but I really only want to remove them from the view, cause I want the default model to remain the same always.
So how I remove and add Templates to the view when the user clicks Addcontent, and remove the specific view when the user clicks remove ? is there a Meteor way to do it ? So a user clicks addContent and a new section template is rendered or removed if clicks remove.
Removing one should be simple enough. First, it might help to add the id of the section in your delete link:
<a data-id="{{section._id}}" class="deleteSection" href='#'>Delete Section</a>
Template.section.events({
'click a.deleteSection': function(evt) {
Sections.remove($(evt.target).data('id'));
}
});
Adding one is a different story, because it depends on exactly what you want to happen when you click the addSection link. Does that link render a form that creates a new Section on submission? Or does the handler to that link create a Section object and fill in the values?
Edit
It was pointed out that Sections is not a collection. I should've seen that. Making it into a collection would be an obvious way to deal with that. So instead of:
{{#each list.section}}
{{> section}}
{{/each}}
You'd have:
{{#each sections list._id}}
{{> section}}
{{/each}}
And in your js file:
Template.main_list_view.helpers({
sections: function(list_id) {
return Sections.find({ list_id: list_id });
}
});
Alternatively, you could move the "section" template up to the parent and then you could add the list_id as a data attribute on the link::
{{#each list.section}}
<li>{{name}}</li>
<li>{{description}}</li>
<a data-id="{{list._id}}" data-name="{{name}}" data-description="{{description}}" class="deleteSection" href="#">Delete Section</a>
{{/each}}
Then in your js file:
Template.section.events({
'click a.deleteSection': function(evt) {
var list = Lists.find($(evt.target).data('id'));
var section = { name: $(evt.target).data('name'), description: $(evt.target).data('description') };
sections = list.sections;
sections.splice(sections.indexOf(section), 1);
Lists.update(list._id, $set: { sections: sections });
}
});

Categories

Resources