Skip to content

Commit

Permalink
chore: release 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ncuillery committed Oct 31, 2014
1 parent 1560d06 commit 8b26603
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 82 deletions.
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
<a name="0.3.0"></a>
## 0.3.0 (2014-10-29)


#### Bug Fixes

* organize state-level options in `ncyBreadcrumb` key instead of `data` ([1ea436d3](http://github.com/ncuillery/angular-breadcrumb/commit/1ea436d3f6d5470b7ae3e71e71259dbd2422bc00), closes [#30](http://github.com/ncuillery/angular-breadcrumb/issues/30))
* curly braces appearing on title of sample app ([855e76cb](http://github.com/ncuillery/angular-breadcrumb/commit/855e76cb33fda607fa3caa230564b77b48262c40))


#### Features

* Add a global option to include abstract states ([6f0461ea](http://github.com/ncuillery/angular-breadcrumb/commit/6f0461ea7db36d8e10c29ed10de1f1c08d215a19), closes [#35](http://github.com/ncuillery/angular-breadcrumb/issues/35), [#28](http://github.com/ncuillery/angular-breadcrumb/issues/28))
* **$breadcrumb:**
* Support url params when using `ncyBreadcrumb.parent` property ([55730045](http://github.com/ncuillery/angular-breadcrumb/commit/55730045dcf3b4fb1048c67f1e18953505563ed4), closes [#46](http://github.com/ncuillery/angular-breadcrumb/issues/46))
* add the customization of the parent state with a function ([ada09015](http://github.com/ncuillery/angular-breadcrumb/commit/ada09015c49f05a94349dabf078f1ed621811aaa), closes [#32](http://github.com/ncuillery/angular-breadcrumb/issues/32))
* **ncyBreadcrumbLast:** Add a new directive rendering the last step ([1eef24fb](http://github.com/ncuillery/angular-breadcrumb/commit/1eef24fbe862a1e3308181c38f50755843cf4426), closes [#37](http://github.com/ncuillery/angular-breadcrumb/issues/37))


#### Breaking Changes

* state-level options has been moved under the custom key
`ncyBreadcrumb` in state's configuration.

To migrate the code follow the example below:
```
// Before
$stateProvider.state('A', {
url: '/a',
data: {
ncyBreadcrumbLabel: 'State A'
}
});
```

```
// After
$stateProvider.state('A', {
url: '/a',
ncyBreadcrumb: {
label: 'State A'
}
});
```
See [API reference](https://github.com/ncuillery/angular-breadcrumb/wiki/API-Reference) for more informations.
([1ea436d3](http://github.com/ncuillery/angular-breadcrumb/commit/1ea436d3f6d5470b7ae3e71e71259dbd2422bc00))


<a name="0.2.3"></a>
### 0.2.3 (2014-07-26)

Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "angular-breadcrumb",
"description": "AngularJS module that generates a breadcrumb from ui-router's states",
"version": "0.2.3",
"version": "0.3.0",
"main": "release/angular-breadcrumb.js",
"ignore": [
"sample",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "angular-breadcrumb",
"description": "AngularJS module that generates a breadcrumb from ui-router's states",
"version": "0.2.3",
"version": "0.3.0",
"homepage": "https://github.com/ncuillery/angular-breadcrumb",
"author": {
"name": "Nicolas Cuillery",
Expand Down
218 changes: 142 additions & 76 deletions release/angular-breadcrumb.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! angular-breadcrumb - v0.2.3
/*! angular-breadcrumb - v0.3.0
* https://github.com/ncuillery/angular-breadcrumb
* Copyright (c) 2014 Nicolas Cuillery; Licensed MIT */

Expand All @@ -11,12 +11,19 @@ function isAOlderThanB(scopeA, scopeB) {
}
}

function parseStateRef(ref) {
var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
if (!parsed || parsed.length !== 4) { throw new Error("Invalid state ref '" + ref + "'"); }
return { state: parsed[1], paramExpr: parsed[3] || null };
}

function $Breadcrumb() {

var $$options = {
prefixStateName: null,
template: 'bootstrap3',
templateUrl: null
templateUrl: null,
includeAbstract : false
};

this.setOptions = function(options) {
Expand All @@ -35,45 +42,52 @@ function $Breadcrumb() {
}
});

// Check if a property in state's data is its own
var $$isStateDataProperty = function(state, property) {
if(!state.data || !state.data[property]) {
return false;
}

var parentState = $$parentState(state);
return !(parentState && parentState.data && parentState.data[property] && state.data[property] === parentState.data[property]);
};

// Get the parent state
var $$parentState = function(state) {
// Check if state has explicit parent OR we try guess parent from its name
var name = state.parent || (/^(.+)\.[^.]+$/.exec(state.name) || [])[1];
// If we were able to figure out parent name then get this state
return name && $state.get(name);
return name;
};

// Add the state in the chain if not already in and if not abstract
var $$addStateInChain = function(chain, state) {
var $$addStateInChain = function(chain, stateRef) {
var conf,
parentParams,
ref = parseStateRef(stateRef);

for(var i=0, l=chain.length; i<l; i+=1) {
if (chain[i].name === state.name) {
return;
}
if (chain[i].name === ref.state) {
return;
}
}

if(!state.abstract && !$$isStateDataProperty(state, 'ncyBreadcrumbSkip')) {
state.ncyBreadcrumbLink = $state.href(state.name, $stateParams || {});
chain.unshift(state);
conf = $state.get(ref.state);
if((!conf.abstract || $$options.includeAbstract) && !(conf.ncyBreadcrumb && conf.ncyBreadcrumb.skip)) {
if(ref.paramExpr) {
parentParams = $lastViewScope.$eval(ref.paramExpr);
}

conf.ncyBreadcrumbLink = $state.href(ref.state, parentParams || $stateParams || {});
chain.unshift(conf);
}
};

// Get the state for the parent step in the breadcrumb
var $$breadcrumbParentState = function(state) {
if($$isStateDataProperty(state, 'ncyBreadcrumbParent')) {
return $state.get(state.data.ncyBreadcrumbParent);
var $$breadcrumbParentState = function(stateRef) {
var ref = parseStateRef(stateRef),
conf = $state.get(ref.state);

if(conf.ncyBreadcrumb && conf.ncyBreadcrumb.parent) {
// Handle the "parent" property of the breadcrumb, override the parent/child relation of the state
var isFunction = typeof conf.ncyBreadcrumb.parent === 'function';
var parentStateRef = isFunction ? conf.ncyBreadcrumb.parent($lastViewScope) : conf.ncyBreadcrumb.parent;
if(parentStateRef) {
return parentStateRef;
}
}

return $$parentState(state);
return $$parentState(conf);
};

return {
Expand All @@ -94,47 +108,79 @@ function $Breadcrumb() {
return $$options.templateUrl;
},

getStatesChain: function() {
getStatesChain: function(exitOnFirst) { // Deliberately undocumented param, see getLastStep
var chain = [];

// From current state to the root
for(var state = $state.$current.self; state && state.name !== ''; state=$$breadcrumbParentState(state)) {
$$addStateInChain(chain, state);
for(var stateRef = $state.$current.self.name; stateRef; stateRef=$$breadcrumbParentState(stateRef)) {
$$addStateInChain(chain, stateRef);
if(exitOnFirst && chain.length) {
return chain;
}
}

// Prefix state treatment
if($$options.prefixStateName) {
var prefixState = $state.get($$options.prefixStateName);
if(!prefixState) {
throw 'Bad configuration : prefixState "' + $$options.prefixStateName + '" unknown';
}

$$addStateInChain(chain, prefixState);
$$addStateInChain(chain, $$options.prefixStateName);
}

return chain;
},

getLastStep: function() {
var chain = this.getStatesChain(true);
return chain.length ? chain[0] : undefined;
},

$getLastViewScope: function() {
return $lastViewScope;
}
};
}];
}

var getExpression = function(interpolationFunction) {
if(interpolationFunction.expressions) {
return interpolationFunction.expressions;
} else {
var expressions = [];
angular.forEach(interpolationFunction.parts, function(part) {
if(angular.isFunction(part)) {
expressions.push(part.exp);
}
});
return expressions;
}
};

var registerWatchers = function(labelWatcherArray, interpolationFunction, viewScope, step) {
angular.forEach(getExpression(interpolationFunction), function(expression) {
var watcher = viewScope.$watch(expression, function() {
step.ncyBreadcrumbLabel = interpolationFunction(viewScope);
});
labelWatcherArray.push(watcher);
});

};

var deregisterWatchers = function(labelWatcherArray) {
angular.forEach(labelWatcherArray, function(deregisterWatch) {
deregisterWatch();
});
labelWatcherArray = [];
};

function BreadcrumbDirective($interpolate, $breadcrumb, $rootScope) {
this.$$templates = {
bootstrap2: '<ul class="breadcrumb">' +
'<li ng-repeat="step in steps | limitTo:(steps.length-1)">' +
'<a href="{{step.ncyBreadcrumbLink}}">{{step.ncyBreadcrumbLabel}}</a> ' +
'<span class="divider">/</span>' +
'</li>' +
'<li ng-repeat="step in steps | limitTo:-1" class="active">' +
'<span>{{step.ncyBreadcrumbLabel}}</span>' +
'<li ng-repeat="step in steps" ng-switch="$last || !!step.abstract" ng-class="{active: $last}">' +
'<a ng-switch-when="false" href="{{step.ncyBreadcrumbLink}}">{{step.ncyBreadcrumbLabel}}</a> ' +
'<span ng-switch-when="true">{{step.ncyBreadcrumbLabel}}</span>' +
'<span class="divider" ng-hide="$last">/</span>' +
'</li>' +
'</ul>',
bootstrap3: '<ol class="breadcrumb">' +
'<li ng-repeat="step in steps" ng-class="{active: $last}" ng-switch="$last">' +
'<li ng-repeat="step in steps" ng-class="{active: $last}" ng-switch="$last || !!step.abstract">' +
'<a ng-switch-when="false" href="{{step.ncyBreadcrumbLink}}">{{step.ncyBreadcrumbLabel}}</a> ' +
'<span ng-switch-when="true">{{step.ncyBreadcrumbLabel}}</span>' +
'</li>' +
Expand All @@ -151,47 +197,16 @@ function BreadcrumbDirective($interpolate, $breadcrumb, $rootScope) {
post: function postLink(scope) {
var labelWatchers = [];

var getExpression = function(interpolationFunction) {
if(interpolationFunction.expressions) {
return interpolationFunction.expressions;
} else {
var expressions = [];
angular.forEach(interpolationFunction.parts, function(part) {
if(angular.isFunction(part)) {
expressions.push(part.exp);
}
});
return expressions;
}
};

var registerWatchers = function(interpolationFunction, scope, step) {
angular.forEach(getExpression(interpolationFunction), function(expression) {
var watcher = scope.$watch(expression, function() {
step.ncyBreadcrumbLabel = interpolationFunction(scope);
});
labelWatchers.push(watcher);
});

};

var deregisterWatchers = function() {
angular.forEach(labelWatchers, function(deregisterWatch) {
deregisterWatch();
});
labelWatchers = [];
};

var renderBreadcrumb = function() {
deregisterWatchers();
deregisterWatchers(labelWatchers);
var viewScope = $breadcrumb.$getLastViewScope();
scope.steps = $breadcrumb.getStatesChain();
angular.forEach(scope.steps, function (step) {
if (step.data && step.data.ncyBreadcrumbLabel) {
var parseLabel = $interpolate(step.data.ncyBreadcrumbLabel);
if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
var parseLabel = $interpolate(step.ncyBreadcrumb.label);
step.ncyBreadcrumbLabel = parseLabel(viewScope);
// Watcher for further viewScope updates
registerWatchers(parseLabel, viewScope, step);
registerWatchers(labelWatchers, parseLabel, viewScope, step);
} else {
step.ncyBreadcrumbLabel = step.name;
}
Expand All @@ -210,7 +225,58 @@ function BreadcrumbDirective($interpolate, $breadcrumb, $rootScope) {
}
BreadcrumbDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];

function BreadcrumbLastDirective($interpolate, $breadcrumb, $rootScope) {

return {
restrict: 'A',
scope: {},
template: '{{ncyBreadcrumbLabel}}',
compile: function(cElement, cAttrs) {

// Override the default template if ncyBreadcrumbLast has a value
var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbLast);
if(template) {
cElement.html(template);
}

return {
post: function postLink(scope) {
var labelWatchers = [];

var renderLabel = function() {
deregisterWatchers(labelWatchers);
var viewScope = $breadcrumb.$getLastViewScope();
var lastStep = $breadcrumb.getLastStep();
if(lastStep) {
scope.ncyBreadcrumbLink = lastStep.ncyBreadcrumbLink;
if (lastStep.ncyBreadcrumb && lastStep.ncyBreadcrumb.label) {
var parseLabel = $interpolate(lastStep.ncyBreadcrumb.label);
scope.ncyBreadcrumbLabel = parseLabel(viewScope);
// Watcher for further viewScope updates
// Tricky last arg: the last step is the entire scope of the directive !
registerWatchers(labelWatchers, parseLabel, viewScope, scope);
} else {
scope.ncyBreadcrumbLabel = lastStep.name;
}
}
};

$rootScope.$on('$viewContentLoaded', function () {
renderLabel();
});

// View(s) may be already loaded while the directive's linking
renderLabel();
}
};

}
};
}
BreadcrumbLastDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];

angular.module('ncy-angular-breadcrumb', ['ui.router.state'])
.provider('$breadcrumb', $Breadcrumb)
.directive('ncyBreadcrumb', BreadcrumbDirective);
.directive('ncyBreadcrumb', BreadcrumbDirective)
.directive('ncyBreadcrumbLast', BreadcrumbLastDirective);
})(window, window.angular);
Loading

0 comments on commit 8b26603

Please sign in to comment.