├── demo ├── demo.css ├── demo.js └── demo.html ├── .gitignore ├── .jshintrc ├── bower.json ├── LICENSE ├── publish.js ├── package.json ├── test ├── karma.conf.js └── codemirror.spec.js ├── CHANGELOG.md ├── gruntFile.js ├── src └── ui-codemirror.js ├── .travis.yml └── README.md /demo/demo.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | height: 256px; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | out/ 4 | dist/ 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": false, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "freeze": true, 7 | "globalstrict": true, 8 | "immed": true, 9 | "latedef": false, 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "maxdepth": 4, 17 | "maxcomplexity": 8, 18 | 19 | "eqnull": false, 20 | "esnext": true, 21 | 22 | "browser": true, 23 | "jasmine": true, 24 | "validthis": true, 25 | 26 | "globals": { 27 | "angular": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-codemirror", 3 | "version": "0.3.0", 4 | "description": "This directive allows you to add CodeMirror to your textarea elements.", 5 | "author": "https://github.com/angular-ui/ui-codemirror/contributors", 6 | "license": "MIT", 7 | "homepage": "http://angular-ui.github.com", 8 | "main": "./src/ui-codemirror.js", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "out", 14 | "test*", 15 | "demo*", 16 | "gruntFile.js", 17 | "package.json" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/angular-ui/ui-codemirror.git" 22 | }, 23 | "dependencies": { 24 | "angular": "^1.3", 25 | "codemirror": "^5.0" 26 | }, 27 | "devDependencies": { 28 | "angular-mocks": "^1.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('doc.ui-codeMirror', ['ui.codemirror', 'prettifyDirective', 'ui.bootstrap', 'plunker' ]) 4 | .controller('CodemirrorCtrl', ['$scope', function ($scope) { 5 | 6 | // The modes 7 | $scope.modes = ['Scheme', 'XML', 'Javascript']; 8 | $scope.mode = $scope.modes[0]; 9 | 10 | 11 | // The ui-codemirror option 12 | $scope.cmOption = { 13 | lineNumbers: true, 14 | indentWithTabs: true, 15 | onLoad: function (_cm) { 16 | 17 | // HACK to have the codemirror instance in the scope... 18 | $scope.modeChanged = function () { 19 | _cm.setOption('mode', $scope.mode.toLowerCase()); 20 | }; 21 | 22 | } 23 | }; 24 | 25 | 26 | // Initial code content... 27 | $scope.cmModel = ';; Scheme code in here.\n' + 28 | '(define (double x)\n\t(* x x))\n\n\n' + 29 | '\n' + 30 | '\n\t\n\t\n\t\n\n\n\n' + 31 | '// Javascript code in here.\n' + 32 | 'function foo(msg) {\n\tvar r = Math.random();\n\treturn "" + r + " : " + msg;\n}'; 33 | 34 | }]); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /publish.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 3 | 'use strict'; 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | 8 | module.exports = function() { 9 | 10 | var js_dependencies = [ 11 | 'bower_components/codemirror/lib/codemirror.js', 12 | 'bower_components/codemirror/mode/scheme/scheme.js', 13 | 'bower_components/codemirror/mode/javascript/javascript.js', 14 | 'bower_components/codemirror/mode/xml/xml.js' 15 | ]; 16 | 17 | var css_dependencies = [ 18 | 'bower_components/codemirror/lib/codemirror.css', 19 | 'bower_components/codemirror/theme/twilight.css' 20 | ]; 21 | 22 | function putThemInVendorDir(filepath) { 23 | return 'vendor/' + path.basename(filepath); 24 | } 25 | 26 | return { 27 | humaName: 'UI.CodeMirror', 28 | repoName: 'ui-codemirror', 29 | inlineHTML: fs.readFileSync(__dirname + '/demo/demo.html'), 30 | inlineJS: fs.readFileSync(__dirname + '/demo/demo.js'), 31 | css: css_dependencies.map(putThemInVendorDir), 32 | js: js_dependencies.map(putThemInVendorDir).concat(['dist/ui-codemirror.js']), 33 | tocopy: css_dependencies.concat(js_dependencies), 34 | bowerData: { 35 | main: ['ui-codemirror.js'] 36 | } 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-codemirror", 3 | "version": "0.3.0", 4 | "description": "This directive allows you to add CodeMirror to your textarea elements.", 5 | "author": "https://github.com/angular-ui/ui-codemirror/contributors", 6 | "license": "MIT", 7 | "homepage": "http://angular-ui.github.com", 8 | "main": "./src/ui-codemirror.js", 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "angular-ui-publisher": "angular-ui/angular-ui-publisher#06e016272e0064eb030f52cffbe00daa48b56e20", 12 | "grunt": "~0.4.2", 13 | "grunt-contrib-connect": "~0.9.0", 14 | "grunt-contrib-copy": "~0.7.0", 15 | "grunt-contrib-jshint": "~0.10.0", 16 | "grunt-contrib-uglify": "~0.6.0", 17 | "grunt-contrib-watch": "~0.6.0", 18 | "grunt-conventional-changelog": "~1.1.0", 19 | "grunt-karma": "~0.9.0", 20 | "grunt-ng-annotate": "0.8.0", 21 | "karma": "~0.12.0", 22 | "karma-chrome-launcher": "~0.1.1", 23 | "karma-coffee-preprocessor": "~0.2.1", 24 | "karma-firefox-launcher": "~0.1.2", 25 | "karma-html2js-preprocessor": "~0.1.0", 26 | "karma-jasmine": "~0.3.0", 27 | "karma-phantomjs-launcher": "~0.1.1", 28 | "karma-requirejs": "~0.2.0", 29 | "karma-script-launcher": "~0.1.0", 30 | "load-grunt-tasks": "~1.0.0", 31 | "requirejs": "~2.1.9" 32 | }, 33 | "scripts": { 34 | "test": "grunt" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/angular-ui/ui-codemirror.git" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Oct 31 2013 13:03:03 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '..', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-mocks/angular-mocks.js', 19 | 'bower_components/codemirror/lib/codemirror.js', 20 | 'src/ui-codemirror.js', 21 | 'test/*.spec.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['dots'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_INFO, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 58 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 59 | // - PhantomJS 60 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 61 | browsers: ['Chrome', 'Firefox', 'PhantomJS'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: false 71 | }); 72 | 73 | 74 | if(process.env.TRAVIS){ 75 | config.set({ 76 | browsers: ['TravisCI_Chrome', 'Firefox', 'PhantomJS'], 77 | customLaunchers: { 78 | TravisCI_Chrome: { 79 | base: 'Chrome', 80 | flags: ['--no-sandbox'] 81 | } 82 | }, 83 | }); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.3.0 (2015-05-04) 3 | 4 | * **bower:** bump to CodeMirror ^5 5 | 6 | 7 | ### 0.2.3 (2015-01-27) 8 | 9 | 10 | #### Bug Fixes 11 | 12 | * error calling scope.$applyAsync ([15b84f04](https://github.com/angular-ui/ui-codemirror/commit/15b84f04b322958c6f00e476d0ef8e872ea98770), closes [#89](https://github.com/angular-ui/ui-codemirror/issues/89)) 13 | 14 | 15 | 16 | ### 0.2.2 (2015-01-07) 17 | 18 | 19 | #### Bug Fixes 20 | 21 | * use of undefined this/scope ([744bff1](https://github.com/angular-ui/ui-codemirror/commit/744bff199f3cd57c0c7333fd73a775b11b3bde6d)) 22 | 23 | 24 | ### 0.2.1 (2015-01-07) 25 | 26 | 27 | 28 | ## 0.2.0 (2014-12-08) 29 | 30 | 31 | #### Bug Fixes 32 | 33 | * digest in progress ([645d6e5d](https://github.com/angular-ui/ui-codemirror/commit/645d6e5da2cfb40afa342cd6822374b2299bba39)) 34 | * refresh codemirror in next event loop ([1c03cacd](https://github.com/angular-ui/ui-codemirror/commit/1c03cacdd30d5b70cb0e8c15b6383fbfddeff6d2), closes [#76](https://github.com/angular-ui/ui-codemirror/issues/76)) 35 | * undefined newValue watched ([f5061497](https://github.com/angular-ui/ui-codemirror/commit/f5061497f465090be4bb53a4b4b6c534c586d214)) 36 | * not watching `ui-codemirror-opts` attribute ([0f5802ed](https://github.com/angular-ui/ui-codemirror/commit/0f5802ed39444b3c3dcf49b5bbcc9fd756833cfe)) 37 | * element not removed when the element gets replaced ([7dfcb070](https://github.com/angular-ui/ui-codemirror/commit/7dfcb0704220d8034647b18e41ffd9ee7904d525)) 38 | * **grunt:** do a standard livereload over the built branch ([a856e085](https://github.com/angular-ui/ui-codemirror/commit/a856e085a0ddff949b15c5cac9ec67b4323d8e13)) 39 | 40 | 41 | #### Features 42 | 43 | * Makes the onChange event handled by ngChange ([fa52ea4e](https://github.com/angular-ui/ui-codemirror/commit/fa52ea4e86b85dc9e2b90996282cb1cc12020d04)) 44 | * allow using it as an element ([42de591d](https://github.com/angular-ui/ui-codemirror/commit/42de591db63711d27b75fa5d345623ab3e472efb)) 45 | * **demo:** update demo to Angular UI Publisher 1.2.x ([18317b7c](https://github.com/angular-ui/ui-codemirror/commit/18317b7c010c80e3eadc4f516eae62d2be837e73), closes [#34](https://github.com/angular-ui/ui-codemirror/issues/34)) 46 | * **directive:** add instance access throught $broadcast event ([14f6954c](https://github.com/angular-ui/ui-codemirror/commit/14f6954c376479ac2108edc0556b48c6b1123953)) 47 | * **publisher:** initial publisher use commit ([a144e2f8](https://github.com/angular-ui/ui-codemirror/commit/a144e2f8b3134df9e4a9ce313778b0086ea82af9)) 48 | 49 | 50 | 51 | ## v0.1.0 (2013-12-28) 52 | 53 | 54 | #### Features 55 | 56 | * **publisher:** initial publisher use commit ([a144e2f8](https://github.com/angular-ui/ui-codemirror/commit/a144e2f8b3134df9e4a9ce313778b0086ea82af9)) 57 | 58 | -------------------------------------------------------------------------------- /gruntFile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 'use strict'; 3 | 4 | require('load-grunt-tasks')(grunt); 5 | 6 | // Default task. 7 | grunt.registerTask('default', ['jshint', 'karma:unit']); 8 | grunt.registerTask('serve', ['connect:continuous', 'karma:continuous', 'watch']); 9 | grunt.registerTask('dist', ['ngAnnotate', 'uglify']); 10 | 11 | // HACK TO ACCESS TO THE COMPONENT-PUBLISHER 12 | function fakeTargetTask(prefix){ 13 | return function(){ 14 | 15 | if (this.args.length !== 1) { 16 | return grunt.log.fail('Just give the name of the ' + prefix + ' you want like :\ngrunt ' + prefix + ':bower'); 17 | } 18 | 19 | var done = this.async(); 20 | var spawn = require('child_process').spawn; 21 | spawn('./node_modules/.bin/gulp', [ prefix, '--branch='+this.args[0] ].concat(grunt.option.flags()), { 22 | cwd : './node_modules/angular-ui-publisher', 23 | stdio: 'inherit' 24 | }).on('close', done); 25 | }; 26 | } 27 | 28 | grunt.registerTask('build', fakeTargetTask('build')); 29 | grunt.registerTask('publish', fakeTargetTask('publish')); 30 | // 31 | 32 | // Project configuration. 33 | grunt.initConfig({ 34 | bower: 'bower_components', 35 | dist : '<%= bower %>/angular-ui-docs', 36 | pkg: grunt.file.readJSON('package.json'), 37 | meta: { 38 | banner: ['/**', 39 | ' * <%= pkg.name %> - <%= pkg.description %>', 40 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>', 41 | ' * @link <%= pkg.homepage %>', 42 | ' * @license <%= pkg.license %>', 43 | ' */', 44 | ''].join('\n') 45 | }, 46 | 47 | watch: { 48 | src: { 49 | files: ['src/*'], 50 | tasks: ['jshint:src', 'karma:unit:run', 'dist', 'build:gh-pages'] 51 | }, 52 | test: { 53 | files: ['test/*.js'], 54 | tasks: ['jshint:test', 'karma:unit:run'] 55 | }, 56 | demo: { 57 | files: ['demo/*', 'publish.js'], 58 | tasks: ['jshint', 'build:gh-pages'] 59 | }, 60 | livereload: { 61 | files: ['out/built/gh-pages/**/*'], 62 | options: { livereload: true } 63 | } 64 | }, 65 | 66 | karma: { 67 | unit: {configFile: 'test/karma.conf.js', singleRun: true}, 68 | server: {configFile: 'test/karma.conf.js'}, 69 | continuous: {configFile: 'test/karma.conf.js', background: true } 70 | }, 71 | 72 | connect: { 73 | options: { 74 | base : 'out/built/gh-pages', 75 | open: true, 76 | livereload: true 77 | }, 78 | server: { options: { keepalive: true } }, 79 | continuous: { options: { keepalive: false } } 80 | }, 81 | 82 | jshint: { 83 | src: { 84 | files:{ src : ['src/*.js', 'demo/**/*.js'] }, 85 | options: { jshintrc: '.jshintrc' } 86 | }, 87 | test: { 88 | files:{ src : [ 'test/*.spec.js', 'publish.js', 'gruntFile.js'] }, 89 | options: grunt.util._.extend({}, grunt.file.readJSON('.jshintrc'), { 90 | node: true, 91 | globals: { 92 | angular: false, 93 | inject: false, 94 | jQuery: false, 95 | 96 | jasmine: false, 97 | afterEach: false, 98 | beforeEach: false, 99 | ddescribe: false, 100 | describe: false, 101 | expect: false, 102 | iit: false, 103 | it: false, 104 | spyOn: false, 105 | xdescribe: false, 106 | xit: false 107 | } 108 | }) 109 | } 110 | }, 111 | 112 | uglify: { 113 | options: {banner: '<%= meta.banner %>'}, 114 | build: { 115 | expand: true, 116 | cwd: 'dist', 117 | src: ['*.js'], 118 | ext: '.min.js', 119 | dest: 'dist' 120 | } 121 | }, 122 | 123 | ngAnnotate: { 124 | main: { 125 | expand: true, 126 | cwd: 'src', 127 | src: ['*.js'], 128 | dest: 'dist' 129 | } 130 | }, 131 | 132 | changelog: { 133 | options: { 134 | dest: 'CHANGELOG.md', 135 | from: grunt.option('from') 136 | } 137 | } 138 | }); 139 | 140 | }; 141 | -------------------------------------------------------------------------------- /src/ui-codemirror.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Binds a CodeMirror widget to a 40 | 41 |
42 | ``` 43 | 44 | as element : 45 | ```xml 46 | 47 | ``` 48 | 49 | 50 | ## Options 51 | 52 | All the [Codemirror configuration options](http://codemirror.net/doc/manual.html#config) can be passed through the directive. 53 | 54 | ```javascript 55 | myAppModule.controller('MyController', [ '$scope', function($scope) { 56 | $scope.editorOptions = { 57 | lineWrapping : true, 58 | lineNumbers: true, 59 | readOnly: 'nocursor', 60 | mode: 'xml', 61 | }; 62 | }]); 63 | ``` 64 | 65 | If you update this variable with the new values, they will be merged and the ui will be updated. 66 | 67 | ```xml 68 | 69 | ``` 70 | 71 | ### Working with ng-model 72 | 73 | The ui-codemirror directive plays nicely with ng-model. 74 | 75 | The ng-model will be watched for to set the CodeMirror document value (by [setValue](http://codemirror.net/doc/manual.html#setValue)). 76 | 77 | _The ui-codemirror directive stores and expects the model value to be a standard javascript String._ 78 | 79 | ### ui-refresh directive 80 | 81 | If you apply the refresh directive to element then any change to do this scope value will result to a [refresh of the CodeMirror instance](http://codemirror.net/doc/manual.html#refresh). 82 | 83 | _The ui-refresh directive expects a scope variable that can be any thing...._ 84 | 85 | ```html 86 |
87 | ``` 88 | 89 | Now you can set the _isSomething_ in the controller scope. 90 | 91 | ```javascript 92 | $scope.isSomething = true; 93 | ``` 94 | 95 | Note: the comparison operator between the old and the new value is "!==" 96 | 97 | 98 | ### CodeMirror instance direct access 99 | 100 | For more interaction with the CodeMirror instance in the directive, we provide a direct access to it. 101 | Using 102 | 103 | ```html 104 |
105 | ``` 106 | 107 | the `$scope.codemirrorLoaded` function will be called with the [CodeMirror editor instance](http://codemirror.net/doc/manual.html#CodeMirror) as first argument 108 | 109 | ```javascript 110 | myAppModule.controller('MyController', [ '$scope', function($scope) { 111 | 112 | $scope.codemirrorLoaded = function(_editor){ 113 | // Editor part 114 | var _doc = _editor.getDoc(); 115 | _editor.focus(); 116 | 117 | // Options 118 | _editor.setOption('firstLineNumber', 10); 119 | _doc.markClean() 120 | 121 | // Events 122 | _editor.on("beforeChange", function(){ ... }); 123 | _editor.on("change", function(){ ... }); 124 | }; 125 | 126 | }]); 127 | ``` 128 | 129 | ## Testing 130 | 131 | We use Karma and jshint to ensure the quality of the code. The easiest way to run these checks is to use grunt: 132 | 133 | ```sh 134 | npm install -g grunt-cli 135 | npm install && bower install 136 | grunt 137 | ``` 138 | 139 | The karma task will try to open Firefox and Chrome as browser in which to run the tests. Make sure this is available or change the configuration in `test\karma.conf.js` 140 | 141 | 142 | ### Grunt Serve 143 | 144 | We have one task to serve them all ! 145 | 146 | ```sh 147 | grunt serve 148 | ``` 149 | 150 | It's equal to run separately: 151 | 152 | * `grunt connect:server` : giving you a development server at [http://localhost:8000/](http://localhost:8000/). 153 | 154 | * `grunt karma:server` : giving you a Karma server to run tests (at [http://localhost:9876/](http://localhost:9876/) by default). You can force a test on this server with `grunt karma:unit:run`. 155 | 156 | * `grunt watch` : will automatically test your code and build your demo. You can demo generation with `grunt build:gh-pages`. 157 | 158 | 159 | ### Dist 160 | 161 | This repo is using the [angular-ui/angular-ui-publisher](https://github.com/angular-ui/angular-ui-publisher). 162 | New tags will automatically trigger a new publication. 163 | To test is locally you can trigger a : 164 | 165 | ```sh 166 | grunt dist build:bower 167 | ``` 168 | 169 | it will put the final files in the _'dist'_ folder and a sample of the bower tag output in the _'out/built/bower'_ folder. 170 | 171 | [travis-url]: https://travis-ci.org/angular-ui/ui-codemirror 172 | [travis-image]: https://travis-ci.org/angular-ui/ui-codemirror.svg?branch=master 173 | [codeclimate-url]: https://codeclimate.com/github/angular-ui/ui-codemirror 174 | [codeclimate-image]: https://codeclimate.com/github/angular-ui/ui-codemirror/badges/gpa.svg 175 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 |
6 | 9 |
10 |
11 |
Code mirror here
12 |
13 | 14 |
20 |
21 | 24 |
25 | 26 | 27 | 28 |
29 |
<section>
 30 |   <div ui-codemirror >Code mirror here</div>
 31 | </section>
32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | 42 |
43 | 46 |
47 |
48 |
<html style="color: green"> 55 | <!-- this is a comment --> 56 | <head> 57 | <title>HTML Example</title> 58 | </head> 59 | <body> 60 | The indentation tries to be <em>somewhat &quot;do what 61 | I mean&quot;</em>... but might not match your style. 62 | </body> 63 | </html>
64 |
65 | 66 |
72 |
73 | 76 |
77 | 78 | 79 | 80 |
81 |
<section>
 82 |   <div ui-codemirror="{
 83 |       lineNumbers: true,
 84 |       theme:'twilight',
 85 |       readOnly: 'nocursor',
 86 |       lineWrapping : true,
 87 |       mode: 'xml'
 88 |     }" >&lt;html style=&quot;color: green&quot;&gt;
 89 | &lt;!-- this is a comment --&gt;
 90 | &lt;head&gt;
 91 | &lt;title&gt;HTML Example&lt;/title&gt;
 92 | &lt;/head&gt;
 93 | &lt;body&gt;
 94 | The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
 95 | I mean&amp;quot;&lt;/em&gt;... but might not match your style.
 96 | &lt;/body&gt;
 97 | &lt;/html&gt;</div>
 98 | </section>
99 |
100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 | 109 |
110 | 113 |
114 |
115 |
116 |
117 |
118 | Mode : 119 |
120 |
121 |
122 | 123 |
124 | 125 |
126 | 127 |
133 |
134 | 137 |
138 | 139 | 140 | 141 |
142 |
<section ng-controller="CodemirrorCtrl">
143 | 
144 |   <textarea ui-codemirror="cmOption" ng-model="cmModel"></textarea>
145 | 
146 |   Mode : <select ng-model="mode" ng-options="m for m in modes" ng-change="modeChanged()"></select>
147 | 
148 | </section>
149 | 150 |
151 |
152 |
153 | 154 |
155 |
app.controller('CodemirrorCtrl', ['$scope', function($scope) {
156 | 
157 |   // The modes
158 |   $scope.modes = ['Scheme', 'XML', 'Javascript'];
159 |   $scope.mode = $scope.modes[0];
160 | 
161 | 
162 |   // The ui-codemirror option
163 |   $scope.cmOption = {
164 |     lineNumbers: true,
165 |     indentWithTabs: true,
166 |     onLoad : function(_cm){
167 | 
168 |       // HACK to have the codemirror instance in the scope...
169 |       $scope.modeChanged = function(){
170 |         _cm.setOption("mode", $scope.mode.toLowerCase());
171 |       };
172 |     }
173 |   };
174 | 
175 | 
176 | 
177 |   // Initial code content...
178 |   $scope.cmModel = ';; Scheme code in here.\n' +
179 |     '(define (double x)\n\t(* x x))\n\n\n' +
180 |     '<!-- XML code in here. -->\n' +
181 |     '<root>\n\t<foo>\n\t</foo>\n\t<bar/>\n</root>\n\n\n' +
182 |     '// Javascript code in here.\n' +
183 |     'function foo(msg) {\n\tvar r = Math.random();\n\treturn "" + r + " : " + msg;\n}';
184 | 
185 | }]);
186 |
187 |
188 |
189 |
190 |
191 | 192 |
193 | -------------------------------------------------------------------------------- /test/codemirror.spec.js: -------------------------------------------------------------------------------- 1 | describe('uiCodemirror', function() { 2 | 'use strict'; 3 | 4 | // declare these up here to be global to all tests 5 | var scope, $compile, $timeout, uiConfig; 6 | var codemirrorDefaults = window.CodeMirror.defaults; 7 | 8 | beforeEach(function() { 9 | module('ui.codemirror'); 10 | 11 | inject(function(_$rootScope_, _$compile_, _$timeout_, uiCodemirrorConfig) { 12 | scope = _$rootScope_.$new(); 13 | $compile = _$compile_; 14 | $timeout = _$timeout_; 15 | uiConfig = uiCodemirrorConfig; 16 | }); 17 | 18 | }); 19 | 20 | afterEach(function() { 21 | uiConfig = {}; 22 | }); 23 | 24 | 25 | it('should not throw an error when window.CodeMirror is defined', function() { 26 | function compile() { 27 | $compile('
')(scope); 28 | } 29 | 30 | var _CodeMirror = window.CodeMirror; 31 | delete window.CodeMirror; 32 | expect(window.CodeMirror).toBeUndefined(); 33 | expect(compile) 34 | .toThrow(new Error('ui-codemirror needs CodeMirror to work... (o rly?)')); 35 | window.CodeMirror = _CodeMirror; 36 | }); 37 | 38 | describe('destruction', function() { 39 | 40 | var parentElement; 41 | 42 | beforeEach(function() { 43 | parentElement = angular.element('
'); 44 | angular.element(document.body).prepend(parentElement); 45 | }); 46 | 47 | afterEach(function() { 48 | parentElement.remove(); 49 | }); 50 | 51 | function shouldDestroyTest(elementType, template) { 52 | it('should destroy the directive of ' + elementType, function() { 53 | var element = angular.element(template); 54 | parentElement.append(element); 55 | 56 | $compile(element)(scope); 57 | scope.$digest(); 58 | 59 | expect(parentElement.children().length).toBe(1); 60 | element.remove(); 61 | scope.$digest(); 62 | expect(parentElement.children().length).toBe(0); 63 | }); 64 | } 65 | 66 | shouldDestroyTest('an element', ''); 67 | shouldDestroyTest('an attribute', '
'); 68 | 69 | }); 70 | 71 | it('should not throw an error when window.CodeMirror is defined an attribute', function() { 72 | function compile() { 73 | $compile('
')(scope); 74 | } 75 | 76 | expect(window.CodeMirror).toBeDefined(); 77 | expect(compile).not.toThrow(); 78 | }); 79 | 80 | 81 | it('should not throw an error when window.CodeMirror is defined an element', function() { 82 | function compile() { 83 | $compile('')(scope); 84 | } 85 | 86 | expect(window.CodeMirror).toBeDefined(); 87 | expect(compile).not.toThrow(); 88 | }); 89 | 90 | 91 | it('should watch all uiCodemirror attribute', function() { 92 | spyOn(scope, '$watch'); 93 | scope.cmOption = {}; 94 | $compile('
')(scope); 95 | expect(scope.$watch.calls.count()).toEqual(3); // The uiCodemirror+ the ngModel + the uiRefresh 96 | expect(scope.$watch).toHaveBeenCalledWith('cmOption', jasmine.any(Function), true); // uiCodemirror 97 | expect(scope.$watch).toHaveBeenCalledWith(jasmine.any(Function)); // ngModel 98 | expect(scope.$watch).toHaveBeenCalledWith('sdf', jasmine.any(Function)); // uiRefresh 99 | }); 100 | 101 | describe('CodeMirror instance', function() { 102 | 103 | var codemirror = null, spies = angular.noop; 104 | 105 | beforeEach(function() { 106 | var _constructor = window.CodeMirror; 107 | window.CodeMirror = jasmine.createSpy('window.CodeMirror') 108 | .and.callFake(function() { 109 | codemirror = _constructor.apply(this, arguments); 110 | spies(codemirror); 111 | return codemirror; 112 | }); 113 | 114 | window.CodeMirror.defaults = codemirrorDefaults; 115 | }); 116 | 117 | 118 | it('should call the CodeMirror constructor with a function', function() { 119 | $compile('
')(scope); 120 | 121 | expect(window.CodeMirror.calls.count()).toEqual(1); 122 | expect(window.CodeMirror) 123 | .toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Object)); 124 | 125 | expect(codemirror).toBeDefined(); 126 | }); 127 | 128 | it('should work as an element', function() { 129 | $compile('')(scope); 130 | 131 | expect(window.CodeMirror.calls.count()).toEqual(1); 132 | expect(window.CodeMirror) 133 | .toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Object)); 134 | 135 | expect(codemirror).toBeDefined(); 136 | }); 137 | 138 | it('should have a child element with a div.CodeMirror', function() { 139 | // Explicit a parent node to support the directive. 140 | var element = $compile('
')(scope).children(); 141 | 142 | expect(element).toBeDefined(); 143 | expect(element.prop('tagName')).toBe('DIV'); 144 | expect(element.prop('classList').length).toEqual(2); 145 | expect(element.prop('classList')[0]).toEqual('CodeMirror'); 146 | expect(element.prop('classList')[1]).toEqual('cm-s-default'); 147 | }); 148 | 149 | 150 | describe('options', function() { 151 | 152 | spies = function(codemirror) { 153 | codemirror._setOption = codemirror._setOption || codemirror.setOption; 154 | codemirror.setOption = jasmine.createSpy('codemirror.setOption') 155 | .and.callFake(function() { 156 | codemirror._setOption.apply(this, arguments); 157 | }); 158 | }; 159 | 160 | it('should not be called', function() { 161 | $compile('
')(scope); 162 | expect(window.CodeMirror) 163 | .toHaveBeenCalledWith(jasmine.any(Function), { value: '' }); 164 | expect(codemirror.setOption).not.toHaveBeenCalled(); 165 | }); 166 | 167 | it('should include the passed options (attribute directive)', function() { 168 | $compile('
')(scope); 169 | 170 | expect(window.CodeMirror) 171 | .toHaveBeenCalledWith(jasmine.any(Function), { 172 | value: '', 173 | oof: 'baar' 174 | }); 175 | expect(codemirror.setOption).not.toHaveBeenCalled(); 176 | }); 177 | 178 | it('should include the passed options (element directive)', function() { 179 | $compile('')(scope); 180 | 181 | expect(window.CodeMirror) 182 | .toHaveBeenCalledWith(jasmine.any(Function), { 183 | value: '', 184 | oof: 'baar' 185 | }); 186 | expect(codemirror.setOption).not.toHaveBeenCalled(); 187 | }); 188 | 189 | it('should include the default options', function() { 190 | uiConfig.codemirror = { bar: 'baz' }; 191 | $compile('
')(scope); 192 | 193 | expect(window.CodeMirror).toHaveBeenCalledWith(jasmine.any(Function), { 194 | value: '', 195 | bar: 'baz' 196 | }); 197 | expect(codemirror.setOption).not.toHaveBeenCalled(); 198 | }); 199 | 200 | it('should extent the default options', function() { 201 | uiConfig.codemirror = { bar: 'baz' }; 202 | $compile('
')(scope); 203 | 204 | expect(window.CodeMirror).toHaveBeenCalledWith(jasmine.any(Function), { 205 | value: '', 206 | oof: 'baar', 207 | bar: 'baz' 208 | }); 209 | expect(codemirror.setOption).not.toHaveBeenCalled(); 210 | }); 211 | 212 | it('should impact codemirror', function() { 213 | uiConfig.codemirror = {}; 214 | $compile('
')(scope); 215 | 216 | expect(window.CodeMirror).toHaveBeenCalledWith(jasmine.any(Function), { 217 | value: '', 218 | theme: 'baar' 219 | }); 220 | expect(codemirror.setOption).not.toHaveBeenCalled(); 221 | 222 | expect(codemirror.getOption('theme')).toEqual('baar'); 223 | }); 224 | }); 225 | 226 | it('should not trigger watch ui-refresh', function() { 227 | spyOn(scope, '$watch'); 228 | $compile('
')(scope); 229 | expect(scope.$watch).not.toHaveBeenCalled(); 230 | }); 231 | 232 | it('should trigger the CodeMirror.refresh() method', function() { 233 | $compile('
')(scope); 234 | 235 | 236 | spyOn(codemirror, 'refresh'); 237 | scope.$apply('bar = null'); 238 | 239 | scope.$apply('bar = false'); 240 | expect(scope.bar).toBeFalsy(); 241 | $timeout.flush(); 242 | expect(codemirror.refresh).toHaveBeenCalled(); 243 | scope.$apply('bar = true'); 244 | expect(scope.bar).toBeTruthy(); 245 | $timeout.flush(); 246 | expect(codemirror.refresh).toHaveBeenCalled(); 247 | scope.$apply('bar = 0'); 248 | expect(scope.bar).toBeFalsy(); 249 | $timeout.flush(); 250 | expect(codemirror.refresh).toHaveBeenCalled(); 251 | scope.$apply('bar = 1'); 252 | expect(scope.bar).toBeTruthy(); 253 | $timeout.flush(); 254 | expect(codemirror.refresh).toHaveBeenCalled(); 255 | 256 | expect(codemirror.refresh.calls.count()).toEqual(4); 257 | }); 258 | 259 | 260 | it('when the IDE changes should update the model', function() { 261 | var element = $compile('
')(scope); 262 | var ctrl = element.controller('ngModel'); 263 | 264 | expect(ctrl.$pristine).toBe(true); 265 | expect(ctrl.$valid).toBe(true); 266 | 267 | var value = 'baz'; 268 | codemirror.setValue(value); 269 | scope.$apply(); 270 | expect(scope.foo).toBe(value); 271 | 272 | expect(ctrl.$valid).toBe(true); 273 | expect(ctrl.$dirty).toBe(true); 274 | 275 | }); 276 | 277 | it('when the model changes should update the IDE', function() { 278 | var element = $compile('
')(scope); 279 | var ctrl = element.controller('ngModel'); 280 | 281 | expect(ctrl.$pristine).toBe(true); 282 | expect(ctrl.$valid).toBe(true); 283 | 284 | scope.$apply('foo = "bar"'); 285 | expect(codemirror.getValue()).toBe(scope.foo); 286 | 287 | expect(ctrl.$pristine).toBe(true); 288 | expect(ctrl.$valid).toBe(true); 289 | }); 290 | 291 | 292 | it('when the IDE changes should use ngChange', function() { 293 | scope.change = angular.noop; 294 | spyOn(scope, 'change').and.callFake(function() { expect(scope.foo).toBe('baz'); }); 295 | 296 | $compile('
')(scope); 297 | 298 | // change shouldn't be called initialy 299 | expect(scope.change).not.toHaveBeenCalled(); 300 | 301 | 302 | // change shouldn't be called when the value change is coming from the model. 303 | scope.$apply('foo = "bar"'); 304 | expect(scope.change).not.toHaveBeenCalled(); 305 | 306 | // change should be called when user changes the input. 307 | codemirror.setValue('baz'); 308 | scope.$apply(); 309 | expect(scope.change.calls.count()).toBe(1); 310 | expect(scope.change).toHaveBeenCalledWith(); 311 | }); 312 | 313 | it('should runs the onLoad callback', function() { 314 | scope.codemirrorLoaded = jasmine.createSpy('scope.codemirrorLoaded'); 315 | 316 | $compile('
')(scope); 317 | 318 | expect(scope.codemirrorLoaded).toHaveBeenCalled(); 319 | expect(scope.codemirrorLoaded).toHaveBeenCalledWith(codemirror); 320 | }); 321 | 322 | it('responds to the $broadcast event "CodeMirror"', function() { 323 | var broadcast = {}; 324 | broadcast.callback = jasmine.createSpy('broadcast.callback'); 325 | 326 | $compile('
')(scope); 327 | scope.$broadcast('CodeMirror', broadcast.callback); 328 | 329 | expect(broadcast.callback).toHaveBeenCalled(); 330 | expect(broadcast.callback).toHaveBeenCalledWith(codemirror); 331 | }); 332 | 333 | 334 | it('should watch the options (attribute directive)', function() { 335 | 336 | scope.cmOption = { readOnly: true }; 337 | $compile('
')(scope); 338 | scope.$digest(); 339 | 340 | expect(codemirror.getOption('readOnly')).toBeTruthy(); 341 | 342 | scope.cmOption.readOnly = false; 343 | scope.$digest(); 344 | expect(codemirror.getOption('readOnly')).toBeFalsy(); 345 | }); 346 | 347 | it('should watch the options (element directive)', function() { 348 | 349 | scope.cmOption = { readOnly: true }; 350 | $compile('')(scope); 351 | scope.$digest(); 352 | 353 | expect(codemirror.getOption('readOnly')).toBeTruthy(); 354 | 355 | scope.cmOption.readOnly = false; 356 | scope.$digest(); 357 | expect(codemirror.getOption('readOnly')).toBeFalsy(); 358 | }); 359 | 360 | it('should watch the options (object property)', function() { 361 | 362 | scope.cm = {}; 363 | scope.cm.option = { readOnly: true }; 364 | $compile('
')(scope); 365 | scope.$digest(); 366 | 367 | expect(codemirror.getOption('readOnly')).toBeTruthy(); 368 | 369 | scope.cm.option.readOnly = false; 370 | scope.$digest(); 371 | expect(codemirror.getOption('readOnly')).toBeFalsy(); 372 | }); 373 | 374 | }); 375 | 376 | it('when the model is an object or an array should throw an error', function() { 377 | function compileWithObject() { 378 | $compile('
')(scope); 379 | scope.foo = {}; 380 | scope.$apply(); 381 | } 382 | 383 | function compileWithArray() { 384 | $compile('
')(scope); 385 | scope.foo = []; 386 | scope.$apply(); 387 | } 388 | 389 | expect(compileWithObject).toThrow(); 390 | expect(compileWithArray).toThrow(); 391 | }); 392 | 393 | }); 394 | --------------------------------------------------------------------------------