├── .editorconfig
├── .gitignore
├── .jshintrc
├── Gruntfile.js
├── LICENSE
├── README.md
├── assets
└── bower
│ ├── LICENSE
│ ├── README.md
│ ├── bower.json
│ └── version.txt
├── example
├── NEWdashboard
│ ├── dashboard.js
│ └── newDashboard.html
├── admin
│ ├── admin.html
│ └── admin.js
├── app.js
├── components.module.js
├── dashboard
│ ├── dashboard.html
│ └── dashboard.js
├── features.json
└── index.html
├── grunt
├── clean.js
├── copy.js
├── jshint.js
└── uglify.js
├── package.json
├── scripts
├── angular-feature-toggle
│ ├── tag-release.sh
│ └── untag-release.sh
├── bower
│ ├── publish.sh
│ ├── repos.inc
│ └── unpublish.sh
├── jenkins
│ ├── master.sh
│ ├── release.sh
│ └── undo-release.sh
└── utils.inc
└── src
└── angular-feature-toggle.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = tab
12 | # indent_size = 4
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDEs
2 | .idea/
3 |
4 | # package managers
5 | bower_components/
6 | node_modules/
7 |
8 | # build elements
9 | .tmp/
10 | dist/
11 | build/
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "browser": true,
4 | "camelcase": true,
5 | "curly": true,
6 | "esnext": true,
7 | "eqeqeq": true,
8 | "eqnull": true,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": "nofunc",
12 | "maxlen": 300,
13 | "maxstatements": 30,
14 | "newcap": true,
15 | "node": true,
16 | "strict": true,
17 | "trailing": true,
18 | "quotmark": "single",
19 | "undef": true,
20 | "unused": true,
21 | "white": true,
22 | "globals": {
23 | "define": false,
24 | "require": false,
25 | "describe": false,
26 | "beforeEach": false,
27 | "afterEach": false,
28 | "it": false,
29 | "expect": false,
30 | "inject": false,
31 | "jasmine": false,
32 | "spyOn": false,
33 | "fdescribe": false,
34 | "xdescribe": false,
35 | "fit": false,
36 | "xit": false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*jshint camelcase: false */
2 |
3 | 'use strict';
4 |
5 | module.exports = function (grunt) {
6 |
7 | // load all grunt tasks
8 | require('load-grunt-tasks')(grunt);
9 |
10 | // load all grunt configuration
11 | require('load-grunt-config')(grunt);
12 |
13 | // Register Tasks
14 | grunt.registerTask('default', 'Start working on this project.', [
15 | 'jshint'
16 | ]);
17 |
18 | // Bower
19 | grunt.registerTask('bower', 'Build production ready assets and views.', [
20 | 'clean',
21 | 'copy:bower'
22 | ]);
23 |
24 | // Build
25 | grunt.registerTask('build', 'Build production ready assets and views.', [
26 | 'clean',
27 | 'copy:dist',
28 | 'uglify'
29 | ]);
30 | };
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Internet Systems Consortium license
2 | ===================================
3 |
4 | Copyright (c) 2015, Mixpo, LLC.
5 |
6 | Permission to use, copy, modify, and/or distribute this software for any purpose
7 | with or without fee is hereby granted, provided that the above copyright notice
8 | and this permission notice appear in all copies.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
16 | THIS SOFTWARE.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://badge.fury.io/js/angular-feature-toggle)
2 |
3 | # angular-feature-toggle
4 |
5 | ## Synopsis
6 | This library lets you manage features in the frontend using a semver notation.
7 | Inspired by [angular-feature-flags](https://github.com/mjt01/angular-feature-flags).
8 |
9 | ## Examples and Demo
10 | Check out the [example](https://github.com/yairhaimo/angular-feature-toggle/tree/master/example) directory for a simple usage of the library or this [plunker](http://plnkr.co/edit/j49u6oqQ6ulppqUphhMq) for the same demo.
11 |
12 | ## Installation
13 | ```sh
14 | npm install angular-feature-toggle --save
15 | or
16 | bower install angular-feature-toggle
17 | ```
18 | ```js
19 | angular.module('main.app', ['yh.featureToggle'])
20 | ```
21 | ## Configuration
22 | angular-feature-toggle uses a semver notation per feature and expects a configuration of this nature:
23 | ```js
24 | {
25 | "feature1": "1.5.1",
26 | "feature2": "0.5.6"
27 | }
28 | ```
29 | This configuration toggles features inside the code according to their version conditions.
30 | ```
31 | //Example for "feature1" : "1.5.1"
32 | //^1.0.0 - true
33 | //~1.5.0 - true
34 | //~1.6.0 - false
35 | //^2 - false
36 | //* - true
37 | ```
38 | For more information regarding the semver notation head over to the [semver](http://semver.org/) and the [node-semver](https://github.com/npm/node-semver) sites.
39 |
40 | **NOTE**: In order to configure itself at angular's config phase angular-feature-toggle is, at the moment, dependant on a property named 'angularFeaturesConf' on the global window object.
41 |
42 | ##### Manual setting
43 | For hardcoded values you can set the property manually:
44 | ```js
45 | window.angularFeaturesConf = {dashboard: '1.0.0', admin: '0.5.1'};
46 | ```
47 | For a dynamic feature loading method take a look at the [serverside feature loading example](#serversideLoading).
48 |
49 |
50 | ## ui.router
51 | angular-feature-toggle detects if ui.router is in use and wraps it with a feature-toggle helper function.
52 | You can now define your states this way:
53 | ```js
54 | $stateProvider
55 | .state('master.dashboard',
56 | {
57 | url: '/dashboard',
58 | templateUrl: 'dashboard/dashboard.html',
59 | controller: 'DashboardController',
60 | controllerAs: 'dashboard',
61 | feature: 'dash', //optional
62 | version: '^0.5.1'
63 | }
64 | )
65 | .state('master.dashboard',
66 | {
67 | url: '/dashboard',
68 | templateUrl: 'NEWdashboard/newDashboard.html',
69 | controller: 'NewDashboardController',
70 | controllerAs: 'dashboard',
71 | feature: 'dash', //optional
72 | version: '^1'
73 | }
74 | );
75 | ```
76 | Note that both states have the same name but different version conditions (***^0.5.1*** vs ***^1***).
77 | If the version is not satisfied for a specific state definition then that definition will be **ignored**.
78 | If more than one version of the same state is satisfied the first one will be defined and a warning message will be logged for the following ones.
79 | The 'feature' property is optional, if omitted the state name is considered to be the feature name.
80 | **NOTE**: if you omit the version parameter in the state definition then a regular state will be defined.
81 |
82 |
83 | ## Toggle features using a directive
84 | There are two directives you can use in order to toggle features: **show-if-feature** and **hide-if-feature**.
85 | Basically they act as ng-if and the opposite of ng-if. They add/remove elements from the DOM if a feature is enabled or satisfies a certain version.
86 | ```html
87 |
88 |
89 | This is the admin panel
90 |
91 |
92 |
93 | Widgets coming soon...
94 |
95 | ```
96 | With a specific version:
97 | ```html
98 |
99 |
100 | This is the admin panel
101 |
102 |
103 |
104 | This is the NEW and improved admin panel
105 |
106 | ```
107 |
108 | ## Toggle features programmatically
109 | You can use the ***featureToggle*** factory to check feature version:
110 | ```js
111 | .controller('HomeController', function(featureToggle) {
112 | if (featureToggle.isEnabled('admin')) {
113 | this.message = 'welcome administrator!';
114 | }
115 | if (featureToggle.isVersion('admin', '^2')) {
116 | this.items = [1,2,3];
117 | }
118 | });
119 | ```
120 | The featureToggle is also a provider and can be used inside ***.config*** blocks:
121 | ```js
122 | .config(function(featureToggleProvider) {
123 | if (featureToggleProvider.isVersion('dashboard', '~3.5.0')) {
124 | // do something
125 | }
126 | });
127 | ```
128 | **NOTE**: since angular-feature-toggle configures itself in a ***.config*** block it must be defined as a module dependancy in order for its .config block to run prior to the one that is using it.
129 |
130 | ## Serverside feature configuration loading
131 | Since we intialize the feature configuration in a ***.config*** block (in order to support state versioning) we cannot use the $http service to load the feature configuration from the server ($http is not injectable into a config block).
132 | What we can do is first load the configuration with vanilla javascript and only once its loaded manually bootstrap the angular applcation:
133 | ```js
134 | angular.element(document).ready(function() {
135 | fetch('/example/features.json').then(function(response) {
136 | response.json().then(function(features) {
137 | window.angularFeaturesConf = features;
138 | angular.bootstrap(document, ['app']);
139 | })
140 | });
141 | });
142 | ```
143 | This example uses the new [***fetch***](https://developers.google.com/web/updates/2015/03/introduction-to-fetch?hl=en) API for brevity but you can use any ajax function you wish.
144 |
145 | ## Limitations and TODOs
146 | * Dependency on a global window property - angularFeaturesConf
147 | * Cannot load feature configuration for a specific user since the feature initialization is done in the config phase
148 | * Reduce library size (uses the entire node-semver library atm, need only a subset of that)
149 |
--------------------------------------------------------------------------------
/assets/bower/LICENSE:
--------------------------------------------------------------------------------
1 | Internet Systems Consortium license
2 | ===================================
3 |
4 | Copyright (c) 2015, Mixpo, LLC.
5 |
6 | Permission to use, copy, modify, and/or distribute this software for any purpose
7 | with or without fee is hereby granted, provided that the above copyright notice
8 | and this permission notice appear in all copies.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
16 | THIS SOFTWARE.
17 |
--------------------------------------------------------------------------------
/assets/bower/README.md:
--------------------------------------------------------------------------------
1 | Bower packaged angular-feature-toggle
2 | =====================================
3 |
4 | This repo is for distribution `bower`. The source for this module is in the
5 | [mixpo angular-feature-toggle repo](https://github.com/Mixpo/angular-feature-toggle).
6 | Please file issues and pull requests against that repo.
7 |
8 | Install
9 | -------
10 |
11 | You can install this package with `bower`.
12 |
13 | ### bower
14 |
15 | ```shell
16 | bower install angular-feature-toggle
17 | ```
18 |
--------------------------------------------------------------------------------
/assets/bower/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-feature-toggle",
3 | "version": "0.2.1",
4 | "description": "feature toggling capabilities for angular",
5 | "license": "ISC",
6 | "main": "./src/angular-feature-toggle.js",
7 | "dependencies": {
8 | "angular": ">= 1.0.8"
9 | },
10 | "ignore": [
11 | "**/.*",
12 | "node_modules",
13 | "bower_components",
14 | "package.json",
15 | "dist",
16 | "lib",
17 | "config",
18 | "example",
19 | "test",
20 | "Gruntfile.js",
21 | "grunt"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "git@github.com:yairhaimo/angular-feature-toggle.git"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/assets/bower/version.txt:
--------------------------------------------------------------------------------
1 | 0.2.1
2 |
--------------------------------------------------------------------------------
/example/NEWdashboard/dashboard.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('app.components')
4 | .config(function($stateProvider, featureToggleProvider) {
5 | $stateProvider
6 | .state('dashboard', {
7 | url: '/dashboard',
8 | templateUrl: 'NEWdashboard/newDashboard.html',
9 | controller: 'NewDashboardController',
10 | controllerAs: 'dashboard',
11 | feature: 'dash',
12 | version: '^1'
13 | }
14 | );
15 | })
16 | .controller('NewDashboardController', function(featureToggle) {
17 | var dashboard = this;
18 | if (featureToggle.isVersion('dashboard', '^1.5')) {
19 | dashboard.message = 'this is the **SUPER NEW** dashboard (dashboard is ^1.5)';
20 | }
21 | else {
22 | dashboard.message = 'this is the **NEW** dashboard (dashboard is ^1 but NOT ^1.5)';
23 | }
24 | })
25 | .directive('dashboardChart',
26 | function() {
27 | return {
28 | restrict: 'AE',
29 | template: 'shown only if admin is enabled
'
30 | };
31 | }
32 | )
33 | .directive('dashboardChart2',
34 | function() {
35 | return {
36 | restrict: 'AE',
37 | template: 'shown only if admin is ^1.2.0
'
38 | };
39 | }
40 | )
41 |
42 | })();
43 |
--------------------------------------------------------------------------------
/example/NEWdashboard/newDashboard.html:
--------------------------------------------------------------------------------
1 | {{dashboard.message}}
2 |
3 |
4 |
--------------------------------------------------------------------------------
/example/admin/admin.html:
--------------------------------------------------------------------------------
1 | {{admin.message}}
2 |
--------------------------------------------------------------------------------
/example/admin/admin.js:
--------------------------------------------------------------------------------
1 | angular.module('app.components')
2 | .config(function($stateProvider, featureToggleProvider) {
3 | $stateProvider
4 | .state('admin', {
5 | url: '/admin',
6 | templateUrl: 'admin/admin.html',
7 | controller: 'AdminController',
8 | controllerAs: 'admin',
9 | version: '*'
10 | }
11 | );
12 | })
13 | .controller('AdminController', function() {
14 | var admin = this;
15 | admin.message = 'this is the admin panel';
16 | });
17 |
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('app', ['ui.router', 'yh.featureToggle', 'app.components'])
5 | .run(function($rootScope, featureToggle) {
6 | $rootScope.featureToggle = featureToggle;
7 | });
8 |
9 |
10 | // get config from server and manually bootstrap
11 | angular.element(document).ready(function() {
12 | fetch('/example/features.json').then(function(response) {
13 | response.json().then(function(features) {
14 | window.angularFeaturesConf = features;
15 | angular.bootstrap(document, ['app']);
16 | })
17 | });
18 | });
19 |
20 |
21 | })();
22 |
--------------------------------------------------------------------------------
/example/components.module.js:
--------------------------------------------------------------------------------
1 | angular.module('app.components', ['ui.router', 'yh.featureToggle']);
2 |
--------------------------------------------------------------------------------
/example/dashboard/dashboard.html:
--------------------------------------------------------------------------------
1 | {{dashboard.message}}
2 |
--------------------------------------------------------------------------------
/example/dashboard/dashboard.js:
--------------------------------------------------------------------------------
1 | angular.module('app.components')
2 | .config(function($stateProvider, featureToggleProvider) {
3 | $stateProvider
4 | .state('dashboard', {
5 | url: '/dashboard',
6 | templateUrl: 'dashboard/dashboard.html',
7 | controller: 'DashboardController',
8 | controllerAs: 'dashboard',
9 | feature: 'dash',
10 | version: '^0'
11 | }
12 | );
13 | })
14 | .controller('DashboardController', function() {
15 | var dashboard = this;
16 | dashboard.message = 'this is the old dashboard (dashboard is ^0)';
17 | });
18 |
--------------------------------------------------------------------------------
/example/features.json:
--------------------------------------------------------------------------------
1 | {
2 | "dash": "1.6.0",
3 | "admin": "1.0.2"
4 | }
5 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | DASHBOARD
9 |
10 |
Admin
11 | (shown only if admin is enabled)
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/grunt/clean.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | dist: {
3 | files: [{
4 | dot: true,
5 | src: [
6 | 'dist'
7 | ]
8 | }]
9 | },
10 | bower: {
11 | files: [{
12 | dot: true,
13 | src: [
14 | 'build'
15 | ]
16 | }]
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/grunt/copy.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | dist: {
3 | files: [{
4 | expand: true,
5 | dot: true,
6 | cwd: 'src/',
7 | src: [
8 | '*.js'
9 | ],
10 | dest: 'dist/'
11 | }]
12 | },
13 | bower: {
14 | files: [{
15 | expand: true,
16 | dot: true,
17 | cwd: 'src/',
18 | src: [
19 | '*.js'
20 | ],
21 | dest: 'build/'
22 | }, {
23 | expand: true,
24 | dot: true,
25 | cwd: 'assets/bower/',
26 | src: [
27 | '**'
28 | ],
29 | dest: 'build/'
30 | }]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/grunt/jshint.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | jshintrc: '.jshintrc'
4 | },
5 | all: [
6 | 'Gruntfile.js'
7 | ]
8 | };
9 |
--------------------------------------------------------------------------------
/grunt/uglify.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | dist: {
3 | files: {
4 | 'dist/angular-feature-toggle.min.js': ['src/angular-feature-toggle.js']
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-feature-toggle",
3 | "version": "0.2.1",
4 | "description": "feature toggling capabilities for angular",
5 | "main": "dist/angular-feature-toggle.min.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/mixpo/angular-feature-toggle.git"
12 | },
13 | "keywords": [
14 | "angular",
15 | "feature-toggle"
16 | ],
17 | "author": "yairhaimo",
18 | "contributors": [{
19 | "name": "Hays Clark", "email": "hclark@mixpo.com"
20 | }],
21 | "license": "ISC",
22 | "dependencies": {},
23 | "devDependencies": {
24 | "grunt": "^0.4.5",
25 | "grunt-cli": "^0.1.13",
26 | "grunt-contrib-clean": "^0.7.0",
27 | "grunt-contrib-copy": "^0.8.2",
28 | "grunt-contrib-jshint": "^0.11.3",
29 | "grunt-contrib-uglify": "^0.11.0",
30 | "load-grunt-config": "^0.19.1",
31 | "load-grunt-tasks": "^3.3.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/scripts/angular-feature-toggle/tag-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Tags a release
4 | # so that travis can do the actual release.
5 |
6 | echo "#############################################"
7 | echo "## Tag angular.feature.toggle for a release #"
8 | echo "#############################################"
9 |
10 | ARG_DEFS=(
11 | "--action=(prepare|publish)"
12 | "--commit-sha=(.*)"
13 | # the version number of the release.
14 | # e.g. 1.2.12 or 1.2.12-rc.1
15 | "--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
16 | "--version-name=(.+)"
17 | )
18 |
19 | function checkVersionNumber() {
20 | BRANCH_PATTERN=$(readJsonProp "package.json" "branchPattern")
21 | if [[ $VERSION_NUMBER != $BRANCH_PATTERN ]]; then
22 | echo "version-number needs to match $BRANCH_PATTERN on this branch"
23 | usage
24 | fi
25 | }
26 |
27 | function init {
28 | cd ../..
29 | checkVersionNumber
30 | TAG_NAME="v$VERSION_NUMBER"
31 | }
32 |
33 | function prepare() {
34 | git tag "$TAG_NAME" -m "chore(release): $TAG_NAME codename($VERSION_NAME)" "$COMMIT_SHA"
35 | }
36 |
37 | function publish() {
38 | # push the tag to github
39 | git push origin $TAG_NAME
40 | }
41 |
42 | source $(dirname $0)/../utils.inc
43 |
--------------------------------------------------------------------------------
/scripts/angular-feature-toggle/untag-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Untags a release.
4 |
5 | echo "###############################################"
6 | echo "## Untag angular.feature.toggle for a release #"
7 | echo "###############################################"
8 |
9 | ARG_DEFS=(
10 | "--action=(prepare|publish)"
11 | # the version number of the release.
12 | # e.g. 1.2.12 or 1.2.12-rc.1
13 | "--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
14 | )
15 |
16 | function init {
17 | TMP_DIR=$(resolveDir ../../.tmp)
18 | TAG_NAME="v$VERSION_NUMBER"
19 | }
20 |
21 | function prepare() {
22 | :
23 | }
24 |
25 | function publish() {
26 | # push the tag deletion to github
27 | tags=`git ls-remote --tags git@github.com:yairhaimo/angular-feature-toggle`
28 | if [[ $tags =~ "refs/tags/v$VERSION_NUMBER^" ]]; then
29 | echo "-- Creating dummy git repo for angular.js with origin remote"
30 | mkdir $TMP_DIR/empty-angular.js
31 | cd $TMP_DIR/empty-angular.js
32 | git init
33 | git remote add origin git@github.com:yairhaimo/angular-feature-toggle.git
34 | git push origin ":$TAG_NAME"
35 | else
36 | echo "-- Tag v$VERSION_NUMBER does not exist on remote. Moving on"
37 | fi
38 | }
39 |
40 | source $(dirname $0)/../utils.inc
41 |
--------------------------------------------------------------------------------
/scripts/bower/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script for updating the bower repos from current local build.
4 |
5 | echo "#################################"
6 | echo "#### Update bower ###############"
7 | echo "#################################"
8 |
9 | ARG_DEFS=(
10 | "--action=(prepare|publish)"
11 | )
12 |
13 | function init {
14 | TMP_DIR=$(resolveDir ../../.tmp)
15 | BUILD_DIR=$(resolveDir ../../build)
16 | NEW_VERSION=$(cat $BUILD_DIR/version.txt)
17 | PROJECT_DIR=$(resolveDir ../..)
18 | # get the npm dist-tag from a custom property (distTag) in package.json
19 | DIST_TAG=$(readJsonProp "$PROJECT_DIR/package.json" "distTag")
20 | }
21 |
22 |
23 | function prepare {
24 | #
25 | # clone repos
26 | #
27 | for repo in "${REPOS[@]}"
28 | do
29 | echo "-- Cloning bower-$repo"
30 | git clone git@github.com:Mixpo/bower-$repo.git $TMP_DIR/bower-$repo
31 | done
32 |
33 |
34 | #
35 | # move the files from the build
36 | #
37 |
38 | for repo in "${REPOS[@]}"
39 | do
40 | if [ -f $BUILD_DIR/$repo.js ] # ignore i18l
41 | then
42 | echo "-- Updating files in bower-$repo"
43 | cp $BUILD_DIR/$repo.* $TMP_DIR/bower-$repo/
44 | fi
45 | done
46 |
47 |
48 | #
49 | # Run local precommit script if there is one
50 | #
51 | for repo in "${REPOS[@]}"
52 | do
53 | if [ -f $TMP_DIR/bower-$repo/precommit.sh ]
54 | then
55 | echo "-- Running precommit.sh script for bower-$repo"
56 | cd $TMP_DIR/bower-$repo
57 | $TMP_DIR/bower-$repo/precommit.sh
58 | cd $SCRIPT_DIR
59 | fi
60 | done
61 |
62 |
63 | #
64 | # update bower.json
65 | # tag each repo
66 | #
67 | for repo in "${REPOS[@]}"
68 | do
69 | echo "-- Updating version in bower-$repo to $NEW_VERSION"
70 | cd $TMP_DIR/bower-$repo
71 | replaceJsonProp "bower.json" "version" ".*" "$NEW_VERSION"
72 | replaceJsonProp "bower.json" "angular.*" ".*" "$NEW_VERSION"
73 | # we don't have a package.json in our bower repos
74 | #replaceJsonProp "package.json" "version" ".*" "$NEW_VERSION"
75 | #replaceJsonProp "package.json" "angular.*" ".*" "$NEW_VERSION"
76 |
77 | git add -A
78 |
79 | echo "-- Committing and tagging bower-$repo"
80 | git commit -m "v$NEW_VERSION"
81 | git tag v$NEW_VERSION
82 | cd $SCRIPT_DIR
83 | done
84 | }
85 |
86 | function publish {
87 | for repo in "${REPOS[@]}"
88 | do
89 | echo "-- Pushing bower-$repo"
90 | cd $TMP_DIR/bower-$repo
91 | git push origin master
92 | git push origin v$NEW_VERSION
93 |
94 | # we don't have a package.json in our bower repos
95 | # don't publish every build to npm
96 | #if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
97 | #echo "-- Publishing to npm as $DIST_TAG"
98 | #npm publish --tag=$DIST_TAG
99 | #fi
100 |
101 | cd $SCRIPT_DIR
102 | done
103 | }
104 |
105 | source $(dirname $0)/repos.inc
106 | source $(dirname $0)/../utils.inc
107 |
--------------------------------------------------------------------------------
/scripts/bower/repos.inc:
--------------------------------------------------------------------------------
1 | #!/bin/false
2 | # -*- mode: sh; -*- vim: set filetype=sh:
3 |
4 | REPOS=(
5 | angular-feature-toggle
6 | )
7 |
--------------------------------------------------------------------------------
/scripts/bower/unpublish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script for removing tags from the bower repos
4 |
5 | echo "#################################"
6 | echo "#### Untag bower ################"
7 | echo "#################################"
8 |
9 | ARG_DEFS=(
10 | "--action=(prepare|publish)"
11 | "--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
12 | )
13 |
14 | function init {
15 | TMP_DIR=$(resolveDir ../../.tmp)
16 | }
17 |
18 | function prepare {
19 | :
20 | }
21 |
22 | function publish {
23 | for repo in "${REPOS[@]}"
24 | do
25 | tags=`git ls-remote --tags git@github.com:yairhaimo/bower-$repo`
26 | if [[ $tags =~ "refs/tags/v$VERSION_NUMBER" ]]; then
27 | echo "-- Creating dummy git repo for bower-$repo with origin remote"
28 | mkdir $TMP_DIR/bower-$repo
29 | cd $TMP_DIR/bower-$repo
30 | git init
31 | git remote add origin git@github.com:yairhaimo/bower-$repo.git
32 | git push origin :v$VERSION_NUMBER
33 | echo "-- Deleting v$VERSION_NUMBER tag from bower-$repo"
34 | cd $SCRIPT_DIR
35 | else
36 | echo "-- No remote tag matching v$VERSION_NUMBER exists on bower-$repo"
37 | fi
38 | done
39 | }
40 |
41 | source $(dirname $0)/repos.inc
42 | source $(dirname $0)/../utils.inc
43 |
--------------------------------------------------------------------------------
/scripts/jenkins/master.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "#################################"
4 | echo "#### Update master ##############"
5 | echo "#################################"
6 |
7 | ARG_DEFS=(
8 | "[--no-test=(true|false)]"
9 | )
10 |
11 | function init {
12 | if [[ ! $VERBOSE ]]; then
13 | VERBOSE=false
14 | fi
15 | VERBOSE_ARG="--verbose=$VERBOSE"
16 |
17 | TMP_DIR=../../.tmp
18 | if [[ -d ${TMP_DIR} ]]; then
19 | printf '%s\n' "Removing ($TMP_DIR)"
20 | rm -rf ${TMP_DIR}
21 | fi
22 | mkdir ${TMP_DIR}
23 | }
24 |
25 | function build {
26 | cd ../..
27 |
28 | if [[ $NO_TEST == "true" ]]; then
29 | npm install --color false
30 | #grunt ci-checks package --no-color
31 | grunt bower --no-color
32 | else
33 | echo "Add test call here."
34 | # ./jenkins_build.sh
35 | fi
36 |
37 | cd $SCRIPT_DIR
38 | }
39 |
40 | function phase {
41 | ACTION_ARG="--action=$1"
42 |
43 | #../code.angularjs.org/publish.sh $ACTION_ARG $VERBOSE_ARG
44 | ../bower/publish.sh $ACTION_ARG $VERBOSE_ARG
45 | }
46 |
47 | function run {
48 | build
49 |
50 | # First prepare all scripts (build, test, commit, tag, ...),
51 | # so we are sure everything is all right
52 | phase prepare
53 | # only then publish to github
54 | phase publish
55 | }
56 |
57 | source $(dirname $0)/../utils.inc
58 |
--------------------------------------------------------------------------------
/scripts/jenkins/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # tags the current commit as a release and publishes all artifacts to
4 | # the different repositories.
5 | # Note: This will also works if the commit is in the past!
6 |
7 | echo "#################################"
8 | echo "#### cut release ############"
9 | echo "#################################"
10 |
11 | ARG_DEFS=(
12 | # require the git dryrun flag so the script can't be run without
13 | # thinking about this!
14 | "--git-push-dryrun=(true|false)"
15 | # The sha to release. Needs to be the same as HEAD.
16 | # given as parameter to double check.
17 | "--commit-sha=(.*)"
18 | # the version number of the release.
19 | # e.g. 1.2.12 or 1.2.12-rc.1
20 | "--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
21 | # the codename of the release
22 | "--version-name=(.+)"
23 | )
24 |
25 | function init {
26 | if [[ $(git rev-parse HEAD) != $(git rev-parse $COMMIT_SHA) ]]; then
27 | echo "HEAD is not at $COMMIT_SHA"
28 | usage
29 | fi
30 |
31 | if [[ ! $VERBOSE ]]; then
32 | VERBOSE=false
33 | fi
34 | VERBOSE_ARG="--verbose=$VERBOSE"
35 | }
36 |
37 | function build {
38 | cd ../..
39 |
40 | npm install --color false
41 | grunt ci-checks package --no-color
42 |
43 | cd $SCRIPT_DIR
44 | }
45 |
46 | function phase {
47 | ACTION_ARG="--action=$1"
48 | ../angular.js/tag-release.sh $ACTION_ARG $VERBOSE_ARG\
49 | --version-number=$VERSION_NUMBER --version-name=$VERSION_NAME\
50 | --commit-sha=$COMMIT_SHA
51 |
52 | if [[ $1 == "prepare" ]]; then
53 | # The build requires the tag to be set already!
54 | build
55 | fi
56 |
57 | ../code.angularjs.org/publish.sh $ACTION_ARG $VERBOSE_ARG
58 | ../bower/publish.sh $ACTION_ARG $VERBOSE_ARG
59 | }
60 |
61 | function run {
62 | # First prepare all scripts (build, commit, tag, ...),
63 | # so we are sure everything is all right
64 | phase prepare
65 | # only then publish to github
66 | phase publish
67 | }
68 |
69 | source $(dirname $0)/../utils.inc
70 |
--------------------------------------------------------------------------------
/scripts/jenkins/undo-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "#################################"
4 | echo "#### undo a release ############"
5 | echo "#################################"
6 |
7 | ARG_DEFS=(
8 | # require the git dryrun flag so the script can't be run without
9 | # thinking about this!
10 | "--git-push-dryrun=(true|false)"
11 | # the version number of the release.
12 | # e.g. 1.2.12 or 1.2.12-rc.1
13 | "--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
14 | )
15 |
16 | function init {
17 | if [[ ! $VERBOSE ]]; then
18 | VERBOSE=false
19 | fi
20 | VERBOSE_ARG="--verbose=$VERBOSE"
21 | }
22 |
23 | function phase {
24 | ACTION_ARG="--action=$1"
25 | VERSION_NUMBER_ARG="--version-number=$VERSION_NUMBER"
26 | ../angular.js/untag-release.sh $ACTION_ARG $VERBOSE_ARG\
27 | --version-number=$VERSION_NUMBER
28 |
29 | ../code.angularjs.org/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
30 | ../bower/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
31 | }
32 |
33 | function run {
34 | # First prepare all scripts (build, commit, tag, ...),
35 | # so we are sure everything is all right
36 | phase prepare
37 | # only then publish to github
38 | phase publish
39 | }
40 |
41 | source $(dirname $0)/../utils.inc
42 |
--------------------------------------------------------------------------------
/scripts/utils.inc:
--------------------------------------------------------------------------------
1 | # This file provides:
2 | # - a default control flow
3 | # * initializes the environment
4 | # * able to mock "git push" in your script and in all sub scripts
5 | # * call a function in your script based on the arguments
6 | # - named argument parsing and automatic generation of the "usage" for your script
7 | # - intercepting "git push" in your script and all sub scripts
8 | # - utility functions
9 | #
10 | # Usage:
11 | # - define the variable ARGS_DEF (see below) with the arguments for your script
12 | # - include this file using `source utils.inc` at the end of your script.
13 | #
14 | # Default control flow:
15 | # 0. Set the current directory to the directory of the script. By this
16 | # the script can be called from anywhere.
17 | # 1. Parse the named arguments
18 | # 2. If the parameter "git_push_dryrun" is set, all calls to `git push` in this script
19 | # or in child scripts will be intercepted so that the `--dry-run` and `--porcelain` is added
20 | # to show what the push would do but not actually do it.
21 | # 3. If the parameter "verbose" is set, the `-x` flag will be set in bash.
22 | # 4. The function "init" will be called if it exists
23 | # 5. If the parameter "action" is set, it will call the function with the name of that parameter.
24 | # Otherwise the function "run" will be called.
25 | #
26 | # Named Argument Parsing:
27 | # - The variable ARGS_DEF defines the valid command arguments
28 | # * Required args syntax: --paramName=paramRegex
29 | # * Optional args syntax: [--paramName=paramRegex]
30 | # * e.g. ARG_DEFS=("--required_param=(.+)" "[--optional_param=(.+)]")
31 | # - Checks that:
32 | # * all arguments match to an entry in ARGS_DEF
33 | # * all required arguments are present
34 | # * all arguments match their regex
35 | # - Afterwards, every parameter value will be stored in a variable
36 | # with the name of the parameter in upper case (with dash converted to underscore).
37 | #
38 | # Special arguments that are always available:
39 | # - "--action=.*": This parameter will be used to execute a function with that name when the
40 | # script is started
41 | # - "--git_push_dryrun=true": This will intercept all calls to `git push` in this script
42 | # or in child scripts so that the `--dry-run` and `--porcelain` is added
43 | # to show what the push would do but not actually do it.
44 | # - "--verbose=true": This will set the `-x` flag in bash so that all calls will be logged
45 | #
46 | # Utility functions:
47 | # - readJsonProp
48 | # - replaceJsonProp
49 | # - resolveDir
50 | # - getVar
51 | # - serVar
52 | # - isFunction
53 |
54 | # always stop on errors
55 | set -e
56 |
57 | function usage {
58 | echo "Usage: ${0} ${ARG_DEFS[@]}"
59 | exit 1
60 | }
61 |
62 |
63 | function parseArgs {
64 | local REQUIRED_ARG_NAMES=()
65 |
66 | # -- helper functions
67 | function varName {
68 | # everything to upper case and dash to underscore
69 | echo ${1//-/_} | tr '[:lower:]' '[:upper:]'
70 | }
71 |
72 | function readArgDefs {
73 | local ARG_DEF
74 | local AD_OPTIONAL
75 | local AD_NAME
76 | local AD_RE
77 |
78 | # -- helper functions
79 | function parseArgDef {
80 | local ARG_DEF_REGEX="(\[?)--([^=]+)=(.*)"
81 | if [[ ! $1 =~ $ARG_DEF_REGEX ]]; then
82 | echo "Internal error: arg def has wrong format: $ARG_DEF"
83 | exit 1
84 | fi
85 | AD_OPTIONAL="${BASH_REMATCH[1]}"
86 | AD_NAME="${BASH_REMATCH[2]}"
87 | AD_RE="${BASH_REMATCH[3]}"
88 | if [[ $AD_OPTIONAL ]]; then
89 | # Remove last bracket for optional args.
90 | # Can't put this into the ARG_DEF_REGEX somehow...
91 | AD_RE=${AD_RE%?}
92 | fi
93 | }
94 |
95 | # -- run
96 | for ARG_DEF in "${ARG_DEFS[@]}"
97 | do
98 | parseArgDef $ARG_DEF
99 |
100 | local AD_NAME_UPPER=$(varName $AD_NAME)
101 | setVar "${AD_NAME_UPPER}_OPTIONAL" "$AD_OPTIONAL"
102 | setVar "${AD_NAME_UPPER}_RE" "$AD_RE"
103 | if [[ ! $AD_OPTIONAL ]]; then
104 | REQUIRED_ARG_NAMES+=($AD_NAME)
105 | fi
106 | done
107 | }
108 |
109 | function readAndValidateArgs {
110 | local ARG_NAME
111 | local ARG_VALUE
112 | local ARG_NAME_UPPER
113 |
114 | # -- helper functions
115 | function parseArg {
116 | local ARG_REGEX="--([^=]+)=?(.*)"
117 |
118 | if [[ ! $1 =~ $ARG_REGEX ]]; then
119 | echo "Can't parse argument $i"
120 | usage
121 | fi
122 |
123 | ARG_NAME="${BASH_REMATCH[1]}"
124 | ARG_VALUE="${BASH_REMATCH[2]}"
125 | ARG_NAME_UPPER=$(varName $ARG_NAME)
126 | }
127 |
128 | function validateArg {
129 | local AD_RE=$(getVar ${ARG_NAME_UPPER}_RE)
130 |
131 | if [[ ! $AD_RE ]]; then
132 | echo "Unknown option: $ARG_NAME"
133 | usage
134 | fi
135 |
136 | if [[ ! $ARG_VALUE =~ ^${AD_RE}$ ]]; then
137 | echo "Wrong format: $ARG_NAME"
138 | usage;
139 | fi
140 |
141 | # validate that the "action" option points to a valid function
142 | if [[ $ARG_NAME == "action" ]] && ! isFunction $ARG_VALUE; then
143 | echo "No action $ARG_VALUE defined in this script"
144 | usage;
145 | fi
146 | }
147 |
148 | # -- run
149 | for i in "$@"
150 | do
151 | parseArg $i
152 | validateArg
153 | setVar "${ARG_NAME_UPPER}" "$ARG_VALUE"
154 | done
155 | }
156 |
157 | function checkMissingArgs {
158 | local ARG_NAME
159 | for ARG_NAME in "${REQUIRED_ARG_NAMES[@]}"
160 | do
161 | ARG_VALUE=$(getVar $(varName $ARG_NAME))
162 |
163 | if [[ ! $ARG_VALUE ]]; then
164 | echo "Missing: $ARG_NAME"
165 | usage;
166 | fi
167 | done
168 | }
169 |
170 | # -- run
171 | readArgDefs
172 | readAndValidateArgs "$@"
173 | checkMissingArgs
174 |
175 | }
176 |
177 | # getVar(varName)
178 | function getVar {
179 | echo ${!1}
180 | }
181 |
182 | # setVar(varName, varValue)
183 | function setVar {
184 | eval "$1=\"$2\""
185 | }
186 |
187 | # isFunction(name)
188 | # - to be used in an if, so return 0 if successful and 1 if not!
189 | function isFunction {
190 | if [[ $(type -t $1) == "function" ]]; then
191 | return 0
192 | else
193 | return 1
194 | fi
195 | }
196 |
197 | # readJsonProp(jsonFile, property)
198 | # - restriction: property needs to be on a single line!
199 | function readJsonProp {
200 | echo $(sed -En 's/.*"'$2'"[ ]*:[ ]*"(.*)".*/\1/p' $1)
201 | }
202 |
203 | # replaceJsonProp(jsonFile, propertyRegex, valueRegex, replacePattern)
204 | # - note: propertyRegex will be automatically placed into a
205 | # capturing group! -> all other groups start at index 2!
206 | function replaceJsonProp {
207 | replaceInFile $1 '"('$2')"[ ]*:[ ]*"'$3'"' '"\1": "'$4'"'
208 | }
209 |
210 | # replaceInFile(file, findPattern, replacePattern)
211 | function replaceInFile {
212 | sed -i .tmp -E "s/$2/$3/" $1
213 | rm $1.tmp
214 | }
215 |
216 | # resolveDir(relativeDir)
217 | # - resolves a directory relative to the current script
218 | function resolveDir {
219 | echo $(cd $SCRIPT_DIR; cd $1; pwd)
220 | }
221 |
222 | function git_push_dryrun_proxy {
223 | echo "## git push dryrun proxy enabled!"
224 | export ORIGIN_GIT=$(which git)
225 |
226 | function git {
227 | local ARGS=("$@")
228 | local RC
229 | if [[ $1 == "push" ]]; then
230 | ARGS+=("--dry-run" "--porcelain")
231 | echo "####### START GIT PUSH DRYRUN #######"
232 | echo "${ARGS[@]}"
233 | fi
234 | if [[ $1 == "commit" ]]; then
235 | echo "${ARGS[@]}"
236 | fi
237 | $ORIGIN_GIT "${ARGS[@]}"
238 | RC=$?
239 | if [[ $1 == "push" ]]; then
240 | echo "####### END GIT PUSH DRYRUN #######"
241 | fi
242 | return $RC
243 | }
244 |
245 | export -f git
246 | }
247 |
248 | function main {
249 | # normalize the working dir to the directory of the script
250 | cd $(dirname $0);SCRIPT_DIR=$(pwd)
251 |
252 | ARG_DEFS+=("[--git-push-dryrun=(true|false)]" "[--verbose=(true|false)]")
253 | parseArgs "$@"
254 |
255 | # --git_push_dryrun argument
256 | if [[ $GIT_PUSH_DRYRUN == "true" ]]; then
257 | git_push_dryrun_proxy
258 | fi
259 |
260 | # --verbose argument
261 | if [[ $VERBOSE == "true" ]]; then
262 | set -x
263 | fi
264 |
265 | if isFunction init; then
266 | init "$@"
267 | fi
268 |
269 | # jump to the function denoted by the --action argument,
270 | # otherwise call the "run" function
271 | if [[ $ACTION ]]; then
272 | $ACTION "$@"
273 | else
274 | run "$@"
275 | fi
276 | }
277 |
278 |
279 | main "$@"
280 |
--------------------------------------------------------------------------------
/src/angular-feature-toggle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create AngularJS semver provider.
3 | */
4 | var semver = require('semver');
5 | (function (semver, angular) {
6 | 'use strict';
7 |
8 | angular
9 | .module('semver', [])
10 | .provider('semver', [function () {
11 | return buildWrapper(semver);
12 | }]);
13 |
14 | /**
15 | * Dynamically builds a semver wrapper object that
16 | * meets AngularJS provider requirements.
17 | *
18 | * @param semver
19 | * @returns {{$get: Function}}
20 | */
21 | function buildWrapper(semver) {
22 | var wrapper = {
23 | $get: function () {
24 | }
25 | };
26 | for (var key in semver) {
27 | wrapper[key] = semver[key];
28 | }
29 | return wrapper;
30 | }
31 | })(semver, window.angular);
32 |
33 |
34 | /**
35 | * Create AngularJS featureToggle provider.
36 | */
37 | (function (window, angular) {
38 | 'use strict';
39 |
40 | var ng = angular.module('angularFeatureToggle', ['semver']);
41 | ng.config(['featureToggleProvider', '$injector', function (featureToggleProvider, $injector) {
42 | initFeatures(featureToggleProvider);
43 | overrideUIRouterStateFn($injector, featureToggleProvider);
44 |
45 | /**
46 | *
47 | * @param featureToggleProvider
48 | */
49 | /* @ngInject */
50 | function initFeatures(featureToggleProvider) {
51 | if (window.angularFeaturesConf) {
52 | featureToggleProvider.init(window.angularFeaturesConf);
53 | }
54 | else {
55 | window.console.warn('could not detect features');
56 | }
57 | }
58 |
59 | /**
60 | * config ui router
61 | *
62 | * @param $injector
63 | * @param featureToggleProvider
64 | */
65 | /* @ngInject */
66 | function overrideUIRouterStateFn($injector, featureToggleProvider) {
67 | try {
68 | var $stateProvider = $injector.get('$stateProvider');
69 |
70 | // the app uses ui.router, configure it
71 | var oldStateFn = $stateProvider.state;
72 | $stateProvider.state = function (name, conf) {
73 | // enable state if feature version is satisfied or undefined
74 | if ((conf.version === undefined) || (featureToggleProvider.isVersion(conf.feature || name, conf.version))) {
75 | try {
76 | return oldStateFn.call($stateProvider, name, conf);
77 | }
78 | catch (e) {
79 | window.console && window.console.warn('state ' + name + ' is already defined'); // jshint ignore:line
80 | return $stateProvider;
81 | }
82 | }
83 | // else return stateProvider for further state declaration chaining
84 | else {
85 | return $stateProvider;
86 | }
87 | };
88 | } catch (e) {
89 | // the app doesnt use ui.router - silent failure
90 | }
91 | }
92 | }]);
93 |
94 | ng.provider('featureToggle', ['semverProvider', function (semverProvider) {
95 | /* jshint validthis:true */
96 | var semver = semverProvider;
97 |
98 | // define Feature model
99 | function Feature(version) {
100 | this.version = version;
101 | }
102 |
103 | Feature.prototype.isVersion = function (versionToCheck) {
104 | return semver.satisfies(this.version, versionToCheck);
105 | };
106 |
107 | Feature.prototype.isEnabled = function () {
108 | return semver.satisfies(this.version, '*');
109 | };
110 |
111 | /////////////////
112 | var features = {};
113 |
114 | var service = {
115 | init: init,
116 | features: features,
117 | isVersion: isVersion,
118 | isEnabled: isEnabled,
119 | $get: featureToggleFactory
120 | };
121 | return service;
122 |
123 | /**
124 | *
125 | * @param featuresObj
126 | */
127 | function init(featuresObj) {
128 | features = featuresObj;
129 | }
130 |
131 | /**
132 | *
133 | * @param feature
134 | * @param versionToCheck
135 | * @returns {*}
136 | */
137 | function isVersion(feature, versionToCheck) {
138 | return semver.satisfies(features[feature], versionToCheck);
139 | }
140 |
141 | /**
142 | *
143 | * @param feature
144 | * @returns {boolean}
145 | */
146 | function isEnabled(feature) {
147 | return !!features[feature];
148 | }
149 |
150 | /**
151 | *
152 | * @returns {{isVersion: isVersion, isEnabled: isEnabled}}
153 | */
154 | function featureToggleFactory() {
155 | return {
156 | isVersion: isVersion,
157 | isEnabled: isEnabled
158 | };
159 | }
160 | }]);
161 |
162 | ng.directive('showIfFeature', ['featureToggle', function (featureToggle) {
163 | var ddo = {
164 | restrict: 'AE',
165 | transclude: 'element',
166 | terminal: true,
167 | priority: 999,
168 | link: link
169 | };
170 |
171 | return ddo;
172 |
173 | /**
174 | *
175 | * @param scope
176 | * @param element
177 | * @param attrs
178 | * @param ctrl
179 | * @param $transclude
180 | */
181 | function link(scope, element, attrs, ctrl, $transclude) {
182 | var featureEl, childScope, featureName;
183 | var featureVersion = '*';
184 | var args = attrs.showIfFeature.split(/\s+/);
185 | featureName = args[0];
186 | if (args.length > 1) {
187 | featureVersion = args[1];
188 | }
189 |
190 | if (featureToggle.isVersion(featureName, featureVersion)) {
191 | childScope = scope.$new();
192 | $transclude(childScope, function (clone) {
193 | featureEl = clone;
194 | element.after(featureEl).remove();
195 | });
196 | } else {
197 | if (childScope) {
198 | childScope.$destroy();
199 | childScope = null;
200 | }
201 | if (featureEl) {
202 | featureEl.after(element).remove();
203 | featureEl = null;
204 | }
205 | }
206 | }
207 | }]);
208 |
209 | ng.directive('hideIfFeature', ['featureToggle', function hideIfFeature(featureToggle) {
210 | var ddo = {
211 | restrict: 'AE',
212 | transclude: 'element',
213 | terminal: true,
214 | priority: 999,
215 | link: link
216 | };
217 |
218 | return ddo;
219 |
220 | /**
221 | *
222 | * @param scope
223 | * @param element
224 | * @param attrs
225 | * @param ctrl
226 | * @param $transclude
227 | */
228 | function link(scope, element, attrs, ctrl, $transclude) {
229 | var featureEl, childScope, featureName;
230 | var featureVersion = '*';
231 | var args = attrs.hideIfFeature.split(/\s+/);
232 | featureName = args[0];
233 | if (args.length > 1) {
234 | featureVersion = args[1];
235 | }
236 |
237 | if (featureToggle.isVersion(featureName, featureVersion)) {
238 | if (childScope) {
239 | childScope.$destroy();
240 | childScope = null;
241 | }
242 | if (featureEl) {
243 | featureEl.after(element).remove();
244 | featureEl = null;
245 | }
246 | } else {
247 | childScope = scope.$new();
248 | $transclude(childScope, function (clone) {
249 | featureEl = clone;
250 | element.after(featureEl).remove();
251 | });
252 | }
253 | }
254 | }]);
255 |
256 | })(window, window.angular);
257 |
--------------------------------------------------------------------------------