├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── README.md ├── bower.json ├── dist ├── angular-bootstrap-affix.js └── angular-bootstrap-affix.min.js ├── karma.conf.js ├── package.json ├── src └── bootstrap-affix.js └── test ├── .jshintrc └── spec └── bootstrap.affix.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": false, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": false, 18 | "strict": true, 19 | "globalstrict": true, 20 | "trailing": true, 21 | "smarttabs": true, 22 | "predef": [ 23 | "angular" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.8' 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 8 | 9 | ### PhantomJS 10 | While Grunt can run the included unit tests via [PhantomJS](http://phantomjs.org/), this shouldn't be considered a substitute for the real thing. Please be sure to test the `test/*.html` unit test file(s) in _actual_ browsers. 11 | 12 | ## Modifying the code 13 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 14 | 15 | Test that Grunt's CLI and Bower are installed by running `grunt --version` and `bower --version`. If the commands aren't found, run `npm install -g grunt-cli bower`. For more information about installing the tools, see the [getting started with Grunt guide](http://gruntjs.com/getting-started) or [bower.io](http://bower.io/) respectively. 16 | 17 | 1. Fork and clone the repo. 18 | 1. Run `npm install` to install all build dependencies (including Grunt). 19 | 1. Run `bower install` to install the front-end dependencies. 20 | 1. Run `grunt` to grunt this project. 21 | 22 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 23 | 24 | ## Submitting pull requests 25 | 26 | 1. Create a new branch, please don't work in your `master` branch directly. 27 | 1. Add failing tests for the change you want to make. Run `grunt` to see the tests fail. 28 | 1. Fix stuff. 29 | 1. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 30 | 1. Open `test/*.html` unit test file(s) in actual browser to ensure tests pass everywhere. 31 | 1. Update the documentation to reflect any changes. 32 | 1. Push to your fork and submit a pull request. 33 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2013-07-11 using generator-angular-component v0.2.1 2 | 'use strict'; 3 | 4 | module.exports = function(grunt) { 5 | 6 | // Configurable paths 7 | var yoConfig = { 8 | livereload: 35729, 9 | src: 'src', 10 | dist: 'dist' 11 | }; 12 | 13 | // Livereload setup 14 | var lrSnippet = require('connect-livereload')({port: yoConfig.livereload}); 15 | var mountFolder = function (connect, dir) { 16 | return connect.static(require('path').resolve(dir)); 17 | }; 18 | 19 | // Load all grunt tasks 20 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 21 | 22 | // Project configuration 23 | grunt.initConfig({ 24 | pkg: grunt.file.readJSON('package.json'), 25 | yo: yoConfig, 26 | meta: { 27 | banner: '/**\n' + 28 | ' * <%= pkg.name %>\n' + 29 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 30 | ' * @link <%= pkg.homepage %>\n' + 31 | ' * @author <%= pkg.author.name %> <<%= pkg.author.email %>>\n' + 32 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 33 | ' */\n' 34 | }, 35 | open: { 36 | server: { 37 | path: 'http://localhost:<%= connect.options.port %>' 38 | } 39 | }, 40 | clean: { 41 | dist: { 42 | files: [{ 43 | dot: true, 44 | src: [ 45 | '.tmp', 46 | '<%= yo.dist %>/*', 47 | '!<%= yo.dist %>/.git*' 48 | ] 49 | }] 50 | }, 51 | server: '.tmp' 52 | }, 53 | watch: { 54 | gruntfile: { 55 | files: '<%= jshint.gruntfile.src %>', 56 | tasks: ['jshint:gruntfile'] 57 | }, 58 | less: { 59 | files: ['<%= yo.src %>/{,*/}*.less'], 60 | tasks: ['less:dist'] 61 | }, 62 | app: { 63 | files: [ 64 | '<%= yo.src %>/{,*/}*.html', 65 | '{.tmp,<%= yo.src %>}/{,*/}*.css', 66 | '{.tmp,<%= yo.src %>}/{,*/}*.js' 67 | ], 68 | options: { 69 | livereload: yoConfig.livereload 70 | } 71 | }, 72 | test: { 73 | files: '<%= jshint.test.src %>', 74 | tasks: ['jshint:test', 'qunit'] 75 | } 76 | }, 77 | connect: { 78 | options: { 79 | port: 9000, 80 | hostname: '0.0.0.0' // Change this to '0.0.0.0' to access the server from outside. 81 | }, 82 | livereload: { 83 | options: { 84 | middleware: function (connect) { 85 | return [ 86 | lrSnippet, 87 | mountFolder(connect, '.tmp'), 88 | mountFolder(connect, yoConfig.src) 89 | ]; 90 | } 91 | } 92 | } 93 | }, 94 | less: { 95 | options: { 96 | // dumpLineNumbers: 'all', 97 | paths: ['<%= yo.src %>'] 98 | }, 99 | dist: { 100 | files: { 101 | '<%= yo.src %>/<%= yo.name %>.css': '<%= yo.src %>/<%= yo.name %>.less' 102 | } 103 | } 104 | }, 105 | jshint: { 106 | gruntfile: { 107 | options: { 108 | jshintrc: '.jshintrc' 109 | }, 110 | src: 'Gruntfile.js' 111 | }, 112 | src: { 113 | options: { 114 | jshintrc: '.jshintrc' 115 | }, 116 | src: ['<%= yo.src %>/*.js'] 117 | }, 118 | test: { 119 | options: { 120 | jshintrc: 'test/.jshintrc' 121 | }, 122 | src: ['test/**/*.js'] 123 | } 124 | }, 125 | karma: { 126 | options: { 127 | configFile: 'karma.conf.js', 128 | browsers: ['PhantomJS'] 129 | }, 130 | unit: { 131 | singleRun: true 132 | }, 133 | server: { 134 | autoWatch: true 135 | } 136 | }, 137 | ngmin: { 138 | options: { 139 | banner: '<%= meta.banner %>' 140 | }, 141 | dist: { 142 | src: ['<%= yo.src %>/*.js'], 143 | dest: '<%= yo.dist %>/<%= pkg.name %>.js' 144 | } 145 | }, 146 | concat: { 147 | options: { 148 | banner: '<%= meta.banner %>', 149 | stripBanners: true 150 | }, 151 | dist: { 152 | src: ['<%= yo.src %>/<%= pkg.name %>.js'], 153 | dest: '<%= yo.dist %>/<%= pkg.name %>.js' 154 | } 155 | }, 156 | uglify: { 157 | options: { 158 | banner: '<%= meta.banner %>' 159 | }, 160 | dist: { 161 | src: '<%= concat.dist.dest %>', 162 | dest: '<%= yo.dist %>/<%= pkg.name %>.min.js' 163 | } 164 | }, 165 | bump: { 166 | options: { 167 | files: ['package.json', 'bower.json'], 168 | commitMessage: 'chore(release): bump v<%= pkg.version %>', 169 | tagName: 'v<%= pkg.version %>', 170 | tagMessage: 'chore(release): bump v<%= pkg.version %>', 171 | commitFiles: ['-a'], 172 | pushTo: 'github' 173 | } 174 | } 175 | }); 176 | 177 | grunt.registerTask('test', [ 178 | 'jshint', 179 | 'karma:unit' 180 | ]); 181 | 182 | grunt.registerTask('build', [ 183 | 'clean:dist', 184 | 'less:dist', 185 | 'ngmin:dist', 186 | 'uglify:dist' 187 | ]); 188 | 189 | grunt.registerTask('release', [ 190 | 'test', 191 | 'bump-only', 192 | 'dist', 193 | 'bump-commit' 194 | ]); 195 | 196 | grunt.registerTask('default', ['build']); 197 | 198 | }; 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bootstrap.affix 2 | 3 | Pure AngularJS component replicating [Twitter Bootstrap's Affix](http://getbootstrap.com/javascript/#affix) component behavior. 4 | The affix behavior enables dynamic pinning of a DOM element during page scrolling when specific conditions are met. 5 | 6 | ## Getting Started 7 | 8 | + Install with bower, `bower install angular-bootstrap-affix --save` 9 | 10 | + Or download the [production version][min] or the [development version][max]. 11 | 12 | [min]: https://raw.github.com/mgcrea/jquery-bootstrap-affix/master/dist/angular-bootstrap-affix.min.js 13 | [max]: https://raw.github.com/mgcrea/jquery-bootstrap-affix/master/dist/angular-bootstrap-affix.js 14 | 15 | In your web page: 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | In your app.js: 25 | 26 | ```js 27 | angular.module('myApp', ['mgcrea.bootstrap.affix']) 28 | ``` 29 | 30 | ## Documentation 31 | 32 | + To easily add affix behavior to any element, just add `bs-affix` to the element you want to spy on. Then use offsets to define when to toggle the pinning of an element on and off. 33 | 34 | + Check [Twitter Bootstrap's Affix](http://getbootstrap.com/javascript/#affix) docs. 35 | 36 | ## Examples 37 | 38 | ```html 39 |
40 |
41 |
42 |
43 | 44 | .affix-top { 45 | top: 30px; // css to define 46 | } 47 | 48 | .affix-bottom { 49 | bottom: 0; // css to define 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-affix", 3 | "version": "0.2.2", 4 | "ignore": [], 5 | "main": [ 6 | "dist/angular-bootstrap-affix.js" 7 | ], 8 | "dependencies": { 9 | "angular-jquery": "~0.2.1" 10 | }, 11 | "devDependencies": { 12 | "angular": "latest", 13 | "angular-mocks": "latest" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dist/angular-bootstrap-affix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Firefox document.body.scrollHeight = 0; 5 | * Because of that the affix-bottom class is constantly added and removed causing flicker. 6 | * This function fixes that. 7 | * Stolen from here: http://james.padolsey.com/snippets/get-document-height-cross-browser/ 8 | */ 9 | function getDocHeight() { 10 | var D = document; 11 | return Math.max( 12 | D.body.scrollHeight, D.documentElement.scrollHeight, 13 | D.body.offsetHeight, D.documentElement.offsetHeight, 14 | D.body.clientHeight, D.documentElement.clientHeight 15 | ); 16 | } 17 | 18 | angular.module('mgcrea.bootstrap.affix', ['mgcrea.jquery']).directive('bsAffix', [ 19 | '$window', 20 | 'dimensions', 21 | function ($window, dimensions) { 22 | var checkPosition = function (instance, el, options) { 23 | 24 | var scrollTop = window.pageYOffset; 25 | var windowHeight = window.innerHeight; 26 | var scrollHeight = getDocHeight();///document.body.scrollHeight; 27 | var position = dimensions.offset.call(el[0]); 28 | var height = dimensions.height.call(el[0]); 29 | var offsetTop = options.offsetTop * 1; 30 | var offsetBottom = options.offsetBottom * 1; 31 | var reset = 'affix affix-top affix-bottom'; 32 | var affix; 33 | if (instance.originTop == null) { 34 | instance.originTop = position.top; 35 | } 36 | if (windowHeight >= height && instance.originTop <= scrollTop) { 37 | affix = 'top'; 38 | } else if (windowHeight <= height && scrollTop >= instance.originTop) { 39 | affix = 'bottom'; 40 | } else { 41 | affix = false; 42 | } 43 | if (instance.affixed === affix) 44 | return; 45 | instance.affixed = affix; 46 | el.removeClass(reset).addClass('' + (affix ? 'affix affix-' + affix : '')); 47 | }; 48 | var checkCallbacks = function (scope, instance, iElement, iAttrs) { 49 | if (instance.affixed) { 50 | if (iAttrs.onUnaffix) 51 | eval('scope.' + iAttrs.onUnaffix); 52 | } else { 53 | if (iAttrs.onAffix) 54 | eval('scope.' + iAttrs.onAffix); 55 | } 56 | }; 57 | return { 58 | restrict: 'EAC', 59 | link: function postLink(scope, iElement, iAttrs) { 60 | var instance = { unpin: null }; 61 | //The binding element must be "scrollable" 62 | angular.element($window).bind('scroll', function () { 63 | checkPosition(instance, iElement, iAttrs); 64 | checkCallbacks(scope, instance, iElement, iAttrs); 65 | }); 66 | angular.element($window).bind('click', function () { 67 | setTimeout(function () { 68 | checkPosition(instance, iElement, iAttrs); 69 | checkCallbacks(scope, instance, iElement, iAttrs); 70 | }, 1); 71 | }); 72 | } 73 | }; 74 | } 75 | ]); -------------------------------------------------------------------------------- /dist/angular-bootstrap-affix.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-bootstrap-affix 3 | * @version v0.2.2 - 2016-08-11 4 | * @link https://github.com/maxisam/angular-bootstrap-affix 5 | * @author Olivier Louvignes (original), maxisam 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | "use strict";angular.module("mgcrea.bootstrap.affix",["mgcrea.jquery"]).directive("bsAffix",["$window","dimensions",function($window,dimensions){var checkPosition=function(a,b,c){var d,e=window.pageYOffset,f=window.innerHeight,g=(document.body.scrollHeight,dimensions.offset.call(b[0])),h=dimensions.height.call(b[0]),i=(1*c.offsetTop,1*c.offsetBottom,"affix affix-top affix-bottom");null==a.originTop&&(a.originTop=g.top),d=f>=h&&a.originTop<=e?"top":h>=f&&e>=a.originTop?"bottom":!1,a.affixed!==d&&(a.affixed=d,b.removeClass(i).addClass(""+(d?"affix affix-"+d:"")))},checkCallbacks=function(scope,instance,iElement,iAttrs){instance.affixed?iAttrs.onUnaffix&&eval("scope."+iAttrs.onUnaffix):iAttrs.onAffix&&eval("scope."+iAttrs.onAffix)};return{restrict:"EAC",link:function(a,b,c){var d={unpin:null};angular.element($window).bind("scroll",function(){checkPosition(d,b,c),checkCallbacks(a,d,b,c)}),angular.element($window).bind("click",function(){setTimeout(function(){checkPosition(d,b,c),checkCallbacks(a,d,b,c)},1)})}}}]); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | JASMINE, 9 | JASMINE_ADAPTER, 10 | 'bower_components/angular/angular.js', 11 | 'bower_components/jquery/jquery.js', 12 | 'bower_components/angular-mocks/angular-mocks.js', 13 | 'src/*.js', 14 | 'test/spec/*.js' 15 | ]; 16 | 17 | // list of files to exclude 18 | exclude = []; 19 | 20 | // test results reporter to use 21 | // possible values: dots || progress || growl 22 | reporters = ['progress']; 23 | 24 | // web server port 25 | port = 8080; 26 | 27 | // cli runner port 28 | runnerPort = 9100; 29 | 30 | // enable / disable colors in the output (reporters and logs) 31 | colors = true; 32 | 33 | // level of logging 34 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 35 | logLevel = LOG_INFO; 36 | 37 | // enable / disable watching file and executing tests whenever any file changes 38 | autoWatch = false; 39 | 40 | // Start these browsers, currently available: 41 | // - Chrome 42 | // - ChromeCanary 43 | // - Firefox 44 | // - Opera 45 | // - Safari (only Mac) 46 | // - PhantomJS 47 | // - IE (only Windows) 48 | browsers = ['Chrome']; 49 | 50 | // If browser does not capture in given timeout [ms], kill it 51 | captureTimeout = 5000; 52 | 53 | // Continuous Integration mode 54 | // if true, it capture browsers, run tests and exit 55 | singleRun = false; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-affix", 3 | "version": "0.2.2", 4 | "description": "bootstrap-affix", 5 | "keywords": [ 6 | "angular" 7 | ], 8 | "homepage": "https://github.com/maxisam/angular-bootstrap-affix", 9 | "bugs": "https://github.com/maxisam/angular-bootstrap-affix", 10 | "author": { 11 | "name": "Olivier Louvignes (original), maxisam", 12 | "email": "maxisam@gmail.com", 13 | "url": "https://github.com/mgcrea" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mgcrea/bootstrap-affix.git" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "MIT" 22 | } 23 | ], 24 | "devDependencies": { 25 | "grunt": "~0.4.1", 26 | "grunt-contrib-jshint": "~0.3.0", 27 | "grunt-contrib-concat": "~0.3.0", 28 | "grunt-contrib-uglify": "~0.2.2", 29 | "grunt-contrib-watch": "~0.4.4", 30 | "grunt-contrib-clean": "~0.4.1", 31 | "grunt-contrib-connect": "~0.3.0", 32 | "grunt-contrib-less": "~0.6.1", 33 | "connect-livereload": "~0.2.0", 34 | "grunt-open": "~0.2.0", 35 | "grunt-karma": "~0.4.5", 36 | "grunt-ngmin": "0.0.3", 37 | "grunt-bump": "~0.0.10", 38 | "matchdep": "~0.1.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/bootstrap-affix.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular 4 | .module("mgcrea.bootstrap.affix", ["mgcrea.jquery"]) 5 | .directive("bsAffix", function($window, dimensions) { 6 | var checkPosition = function(instance, el, options) { 7 | var scrollTop = window.pageYOffset; 8 | var windowHeight = window.innerHeight; 9 | var scrollHeight = document.body.scrollHeight; 10 | var position = dimensions.offset.call(el[0]); 11 | var height = dimensions.height.call(el[0]); 12 | var offsetTop = options.offsetTop * 1; 13 | var offsetBottom = options.offsetBottom * 1; 14 | var reset = "affix affix-top affix-bottom"; 15 | var affix; 16 | if (instance.originTop == null) { 17 | instance.originTop = position.top; 18 | } 19 | if (windowHeight >= height && instance.originTop <= scrollTop) { 20 | affix = "top"; 21 | } else if (windowHeight <= height && scrollTop >= instance.originTop) { 22 | affix = "bottom"; 23 | } else { 24 | affix = false; 25 | } 26 | if (instance.affixed === affix) return; 27 | instance.affixed = affix; 28 | 29 | el 30 | .removeClass(reset) 31 | .addClass("" + (affix ? "affix affix-" + affix : "")); 32 | }; 33 | 34 | var checkCallbacks = function(scope, instance, iElement, iAttrs) { 35 | if (instance.affixed) { 36 | if (iAttrs.onUnaffix) eval("scope." + iAttrs.onUnaffix); 37 | } else { 38 | if (iAttrs.onAffix) eval("scope." + iAttrs.onAffix); 39 | } 40 | }; 41 | 42 | return { 43 | restrict: "EAC", 44 | link: function postLink(scope, iElement, iAttrs) { 45 | var instance = { unpin: null }; 46 | 47 | angular.element($window).bind("scroll", function() { 48 | checkPosition(instance, iElement, iAttrs); 49 | checkCallbacks(scope, instance, iElement, iAttrs); 50 | }); 51 | 52 | angular.element($window).bind("click", function() { 53 | setTimeout(function() { 54 | checkPosition(instance, iElement, iAttrs); 55 | checkCallbacks(scope, instance, iElement, iAttrs); 56 | }, 1); 57 | }); 58 | } 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": false, 7 | "curly": false, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": false, 18 | "strict": true, 19 | "globalstrict": true, 20 | "trailing": true, 21 | "smarttabs": true, 22 | "predef": [ 23 | "$", 24 | "angular", 25 | "describe", 26 | "beforeEach", 27 | "afterEach", 28 | "inject", 29 | "it", 30 | "expect" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/spec/bootstrap.affix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Module: bootstrap.affix', function () { 4 | var scope, $sandbox, $compile, $timeout; 5 | 6 | // load the controller's module 7 | beforeEach(module('mgcrea.bootstrap.affix')); 8 | 9 | beforeEach(inject(function ($injector, $rootScope, _$compile_, _$timeout_) { 10 | scope = $rootScope; 11 | $compile = _$compile_; 12 | $timeout = _$timeout_; 13 | 14 | $sandbox = $('
').appendTo($('body')); 15 | })); 16 | 17 | afterEach(function() { 18 | $sandbox.remove(); 19 | scope.$destroy(); 20 | }); 21 | 22 | var templates = { 23 | 'default': { 24 | scope: {}, 25 | element: '
' 26 | } 27 | }; 28 | 29 | function compileDirective(template) { 30 | template = template ? templates[template] : templates['default']; 31 | angular.extend(scope, template.scope || templates['default'].scope); 32 | var $element = $(template.element).appendTo($sandbox); 33 | $element = $compile($element)(scope); 34 | scope.$digest(); 35 | return $element; 36 | } 37 | 38 | it('should correctly display hello world', function () { 39 | var elm = compileDirective(); 40 | expect(elm.text()).toBe('hello world'); 41 | }); 42 | 43 | }); 44 | --------------------------------------------------------------------------------