Closed enkodellc closed 10 years ago
I did come up with a workaround... but it will only work for my situation since I plan on using my abstract states for each module in a similar fashion.
if (displayName !== false) {
var myRoute = currentState.abstract ? currentState.name + '.all ' + currentState.name : currentState.name;
breadcrumbs.push({
displayName: displayName,
route: myRoute
});
}
Hi,
Good point - I didn't account for abstract states. Thanks for posting your work around, and I'll think about how a general solution could be implemented. If you come up with anything more general, feel free to post it or even do a pull request.
Idea: Your solution involves re-assigning any breadcrumb of an abstract state to an alternative non-abstract (concrete?) state. In your case, you have a particular convention that makes it work out for you.
More generally, we could adapt that concept but make the redirection user-definable in the $state object. Something along the lines of this:
$stateProvider
.state('articles', {
url: '',
abstract: true,
template: '<ui-view/>',
resolve: {
loggedin: checkLoggedin
},
data: {
breadcrumbsProxy: 'articles.all'
}
})
.state('articles.all articles', {
url: '/articles',
templateUrl: 'articles/views/list.html',
data: {
displayName: 'Articles'
}
})
So we put a user-defined property called (for example, off the top of my head) breadcrumbsProxy
, which tells the directive which state to substitute for the abstract one. The name of the property could be specified as an attribute, much in the same way as is currently done with the displayname-property
.
When I get a bit of time I'll try to implement something along those lines.
Michael that sounds like a great plan. I might have some time today to try and implement and test it with my project.
Micheel, I am new to Angular and am having difficulty understanding how the code is retrieving the displayname property from the data object. I am trying to replicate that functionality for the breadcrumbProxy but am failing miserably. Can you point me in the right direction on the methodology or angularjs functionality to obtain the displayname from the data object in ui-router.
Reading over the code myself, I even had to take a minute to figure out what was going on, so don't feel bad about not getting it!
Here's an explanation of what's going on:
function getDisplayName(currentState) {
var i;
var propertyReference;
var propertyArray;
var displayName;
if (!scope.displaynameProperty) {
// if the displayname-property attribute was not specified, default to the state's name
return currentState.name;
}
propertyArray = scope.displaynameProperty.split('.');
propertyReference = currentState;
for (i = 0; i < propertyArray.length; i ++) {
if (angular.isDefined(propertyReference[propertyArray[i]])) {
if (propertyReference[propertyArray[i]] === false) {
return false;
} else {
propertyReference = propertyReference[propertyArray[i]];
}
} else {
// if the specified property was not foundm default to the state's name
return currentState.name;
}
}
// use the $interpolate service to handle any bindings in the propertyReference string.
displayName = $interpolate(propertyReference)(currentState.locals.globals);
return displayName;
}
Let's assume the following setup:
.state('articles.article by id', {
url: '/articles/:articleId',
templateUrl: 'articles/views/view.html',
data: {
displayName: '{{ article.title }}'
}
})
<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>
scope.displaynameProperty
comes from our attribute on the element, and is created on the scope for us by the scope: {displaynameProperty: '@'}
in our directive definition object.['data', 'displayName']
. for``loop then traverses down the current
$statedefinition object, checking if firstly the "data" property is defined on it. In our example the answer is "yes", and the value of it would be:
{ displayName : '{{ article.title }}' }`. We then set that result as the object to check in the next iteration.displayName
) is defined on the new object obtained from the first iteration. It is, and its value is the string `'{{ article.title }}'.'{{ article.title }}
into the actual value of the object article.title
, we need to use the Angular service $interpolate. That service takes an Angular expression which we want to interpolate ({{ article.title }}
), and returns a function that then takes a context that is used to look up the value. By "context" is meant basically an object which is used to loop up the value to put into the {{ expression }}
. currentState.locals.globals
- which is basically a pointer to resolve
property on our config object. So this allows it to look in that object, find the article
object, and return the value of its property title
.I hope I made that easy to follow. If you implement something similar, but for the "beadcrumbsProxy" idea, I'd suggest that a bunch of the logic from the above function could be shared by the new function to avoid repetition. Good luck!
I implemented the fix suggested above - see https://github.com/michaelbromley/angularUtils/commit/e7adb8299313b2f2d2c16b41eda044135edf7c91 (plus a few subsequent tweaks)
See the updated docs: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs#working-with-abstract-states
The Plunker demo is also updated to demonstrate abstract states: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview
I got around to implementing your updates. I simplified the updateBreadcrumbsArray() a bit to get the expected output I wanted. I am not concerned about duplicate states and your demo doesn't seem to include the abstract state breadcrumb? Here is my function that I will be using. Thanks for your updates.
/**
* Start with the current state and traverse up the path to build the
* array of breadcrumbs that can be used in an ng-repeat in the template.
*/
function updateBreadcrumbsArray() {
var breadcrumbs = [];
var currentState = $state.$current;
while(currentState && currentState.name !== '') {
var workingState = getWorkingState(currentState);
var displayName = getDisplayName(currentState);
if (displayName !== false) {
breadcrumbs.push({
displayName: displayName,
route: workingState.name
});
}
currentState = currentState.parent;
}
breadcrumbs.reverse();
scope.breadcrumbs = breadcrumbs;
}
Hi, okay - looks good. In the Plunker demo, the 'home.users' state is abstract, and it delegates to the 'home.user.list' state. Thus, when you are in the 'home.users.detail' state, clicking on 'Users' in the breadcrumbs will take you to 'users.list'.
@michaelbromley sorry, yours works great, I was mistaken. Awesome job.
Sorry for resurrecting this old thread, how can I do if I want to show the abstract state (done) but remove the href from it? making it plain text and non-clickable?
@dannygoncalves Do do this quickly I would for the uibreadcrumb and create either a custom property or do a simple trick on the displayname.
For instance let's say you wanted to have your "Todo" breadcrumb to be only a link then instead of displayName: 'Todo' make it displayName: '[-]Todo' then update the following functions:
function stateAlreadyInBreadcrumbs(state, breadcrumbs) {
var i;
var alreadyUsed = false;
for(i = 0; i < breadcrumbs.length; i++) {
//check if route exists
if (breadcrumbs[i].route && (breadcrumbs[i].route === state.name)) {
alreadyUsed = true;
}
}
return alreadyUsed;
}
Inside updateBreadcrumbsArray..
//custom logic to not add the route and so it is text only
if (displayName.indexOf('[-]') !== -1) {
breadcrumbs.push({
displayName: displayName.replace('[-]','')
});
} else {
breadcrumbs.push({
displayName: displayName,
route: workingState.name
});
}
I tested this out and it works. You could add an additional property like textonlyProperty: '@' and check for that before you push the breadcrumb.
I am running into an issue with the breadcrumbs when the parent states are abstract. Below is an example using the mean.io boilerplate. I updated the routes to use your breadcrumb directive. The issue I run into is that the "Articles" breadcrumb is an abstract state and therefore be used as a route. I get this error: "Cannot transition to abstract state 'article'" What do you suggest for a workaround? I would think that more devs besides me would want to use abstract states to build the breadcrumbs... Thanks.