├── .gitignore
├── Gruntfile.js
├── LICENSE
├── README.md
├── demo
├── assets
│ ├── bootstrap-responsive.css
│ ├── demo.css
│ ├── img
│ │ ├── glyphicons-halflings-white.png
│ │ └── glyphicons-halflings.png
│ ├── plunker.js
│ ├── rainbow-generic.js
│ ├── rainbow-html.js
│ ├── rainbow-javascript.js
│ ├── rainbow.css
│ ├── rainbow.js
│ └── ui-bootstrap-tpls-0.1.0-SNAPSHOT.min.js
└── demo-template.html
├── grunt.js
├── karma.conf.js
├── lib
├── angular-mocks.js
├── angular.js
├── helpers.js
└── jquery-1.8.2.min.js
├── package.json
└── src
├── img
├── docs
│ ├── demo.html
│ ├── demo.js
│ └── readme.md
├── img.js
└── test
│ └── imgSpec.js
└── txt
├── docs
├── demo.html
├── demo.js
└── readme.md
├── test
└── txtSpec.js
└── txt.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw*
2 | *~
3 | node_modules/
4 | dist/
5 |
6 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var markdown = require('node-markdown').Markdown;
2 |
3 | module.exports = function(grunt) {
4 |
5 | grunt.loadNpmTasks('grunt-karma');
6 | grunt.loadNpmTasks('grunt-contrib-copy');
7 | grunt.loadNpmTasks('grunt-contrib-watch');
8 | grunt.loadNpmTasks('grunt-contrib-concat');
9 | grunt.loadNpmTasks('grunt-contrib-jshint');
10 | grunt.loadNpmTasks('grunt-contrib-uglify');
11 |
12 | // Project configuration.
13 | grunt.initConfig({
14 | ngversion: '1.1.4',
15 | srcModules: [], //to be filled in by find-modules task
16 | pkg: grunt.file.readJSON('package.json'),
17 | dist: 'dist',
18 | filename: 'placeholders',
19 | meta: {
20 | modules: 'angular.module("placeholders", [<%= srcModules %>]);',
21 | all: 'angular.module("placeholders", [<%= srcModules %>]);'
22 | },
23 | watch: {
24 | js: {
25 | //nospawn makes the tests start faster
26 | nospawn: true,
27 | files: ['src/**/*.js'],
28 | //we don't need to jshint here, it slows down everything else
29 | tasks: ['karma:unit:run']
30 | }
31 | },
32 | concat: {
33 | dist: {
34 | options: {
35 | banner: '<%= meta.modules %>\n'
36 | },
37 | src: [],
38 | dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
39 | }
40 | },
41 | uglify: {
42 | dist:{
43 | src:['<%= dist %>/<%= filename %>-<%= pkg.version %>.js'],
44 | dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
45 | }
46 | },
47 | copy: {
48 | assets: {
49 | files: [
50 | {
51 | src: [ '**' ],
52 | dest: '<%= dist %>/assets/',
53 | cwd: 'demo/assets',
54 | expand: true
55 | }
56 | ]
57 | }
58 | },
59 | jshint: {
60 | files: ['Gruntfile.js','src/**/*.js'],
61 | options: {
62 | curly: true,
63 | immed: true,
64 | newcap: true,
65 | noarg: true,
66 | sub: true,
67 | boss: true,
68 | eqnull: true,
69 | globals: {
70 | angular: true
71 | }
72 | }
73 | },
74 | karma: {
75 | options: {
76 | configFile: 'karma.conf.js'
77 | },
78 | unit: {
79 | background: true
80 | },
81 | continuous: {
82 | singleRun: true
83 | }
84 | }
85 | });
86 |
87 | grunt.renameTask( 'watch', 'delta' );
88 | grunt.registerTask( 'watch', [ 'default', 'karma:unit', 'delta' ] );
89 |
90 | //register before and after test tasks so we've don't have to change cli options on the goole's CI server
91 | grunt.registerTask('before-test', ['jshint']);
92 | grunt.registerTask('after-test', ['build', 'site']);
93 | grunt.registerTask('site', ['index', 'copy']);
94 |
95 | // Default task.
96 | grunt.registerTask('default', ['before-test', 'karma:continuous', 'after-test']);
97 |
98 | //Common placeholders module containing all modules for src and templates
99 | //findModule: Adds a given module to config
100 | function findModule(name) {
101 | function enquote(str) {
102 | return '"' + str + '"';
103 | }
104 | var srcModules = grunt.config('srcModules');
105 |
106 | grunt.file.expand('src/' + name + '/*.js').forEach(function(file) {
107 | srcModules.push(enquote('placeholders.' + name));
108 | });
109 |
110 | grunt.config('srcModules', srcModules);
111 | }
112 |
113 | grunt.registerTask('dist', 'Override dist directory', function() {
114 | var dir = this.args[0];
115 | if (dir) { grunt.config('dist', dir); }
116 | });
117 |
118 | function dependenciesForModule(name) {
119 | var deps = [];
120 | grunt.file.expand('src/' + name + '/*.js')
121 | .map(grunt.file.read)
122 | .forEach(function(contents) {
123 | //Strategy: find where module is declared,
124 | //and from there get everything inside the [] and split them by comma
125 | var moduleDeclIndex = contents.indexOf('angular.module(');
126 | var depArrayStart = contents.indexOf('[', moduleDeclIndex);
127 | var depArrayEnd = contents.indexOf(']', depArrayStart);
128 | var dependencies = contents.substring(depArrayStart + 1, depArrayEnd);
129 | dependencies.split(',').forEach(function(dep) {
130 | if (dep.indexOf('placeholders.') > -1) {
131 | var depName = dep.trim().replace('placeholders.','').replace(/['"]/g,'');
132 | if (deps.indexOf(depName) < 0) {
133 | deps.push(depName);
134 | //Get dependencies for this new dependency
135 | deps = deps.concat(dependenciesForModule(depName));
136 | }
137 | }
138 | });
139 | });
140 | return deps;
141 | }
142 | grunt.registerTask('build', 'Create bootstrap build files', function() {
143 |
144 | var srcFiles = [];
145 | if (this.args.length) {
146 | var modules = [].concat(this.args);
147 | //Find dependencies
148 | this.args.forEach(function(moduleName) {
149 | modules = modules.concat(dependenciesForModule(moduleName));
150 | findModule(moduleName);
151 | });
152 | srcFiles = modules.map(function(name) {
153 | return 'src/' + name + '/*.js';
154 | });
155 | grunt.config('filename', grunt.config('filename')+'-custom');
156 |
157 | } else {
158 | srcFiles = ['src/*/*.js'];
159 |
160 | var folders = grunt.file.expand({filter: 'isDirectory', cwd: '.'}, 'src/*');
161 |
162 | folders.forEach(function(dir) {
163 | findModule(dir.split('/')[1]);
164 | });
165 | }
166 | grunt.config('concat.dist.src', grunt.config('concat.dist.src').concat(srcFiles));
167 |
168 | grunt.task.run(['concat', 'uglify']);
169 | });
170 |
171 | grunt.registerTask('index', 'Create grunt demo site from every module\'s files', function() {
172 |
173 | function breakup(text, separator) {
174 | return text.replace(/[A-Z]/g, function (match) {
175 | return separator + match;
176 | });
177 | }
178 |
179 | function ucwords(text) {
180 | return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
181 | return $1.toUpperCase();
182 | });
183 | }
184 |
185 | var modules = grunt.file.expand({filter: 'isDirectory'}, 'src/*').map(function(dir) {
186 | var moduleName = dir.split("/")[1];
187 | if (grunt.file.isDir(dir + "/docs")) {
188 | return {
189 | name: moduleName,
190 | displayName: ucwords(breakup(moduleName, ' ')),
191 | js: grunt.file.expand(dir + "/docs/*.js").map(grunt.file.read).join(''),
192 | html: grunt.file.expand(dir + "/docs/*.html").map(grunt.file.read).join(''),
193 | description: grunt.file.expand(dir + "/docs/*.md").map(grunt.file.read).map(markdown).join('')
194 | };
195 | }
196 | }).filter(function(module){
197 | return module !== undefined;
198 | });
199 |
200 | grunt.file.write(
201 | 'dist/index.html',
202 | grunt.template.process(grunt.file.read('demo/demo-template.html'), {data: {
203 | modules: modules,
204 | filename: grunt.config('filename'),
205 | version : grunt.config('pkg.version'),
206 | ngversion: grunt.config('ngversion')
207 | }})
208 | );
209 | });
210 | };
211 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Placeholders for AngularJS!
2 |
3 | See the [demo](http://joshdmiller.github.com/angular-placeholders)!
4 |
5 | ---
6 |
7 | ## Placeholder Text
8 |
9 | angular-placeholders includes a `phTxt` directive for the insertion of
10 | "Lorem ipsum"-style text. It can work as either an element or an attribute and
11 | accepts two optional attributes: `num-sentences` and `num-paragraphs`. If
12 | `num-sentences` is provided, the body of the element will be replaced with the
13 | specified number of sentences. `num-paragraphs` will replace the body of the
14 | element with the specified number of `
');
8 | var addField = function (name, value) {
9 | var input = angular.element('');
10 | input.attr('value', value);
11 | form.append(input);
12 | };
13 |
14 | var indexContent = function (content, version) {
15 | return '\n' +
16 | '\n' +
17 | ' \n' +
18 | ' \n' +
19 | ' \n' +
20 | ' \n' +
21 | ' \n' +
22 | ' \n' +
23 | ' \n\n' +
24 | content + '\n' +
25 | ' \n' +
26 | '\n';
27 | };
28 |
29 | var scriptContent = function(content) {
30 | return "angular.module('plunker', ['placeholders']);" + "\n" + content;
31 | };
32 |
33 | addField('description', 'http://joshdmiller.github.com/angular-placeholders/');
34 | addField('files[index.html]', indexContent(content.markup, version));
35 | addField('files[example.js]', scriptContent(content.javascript));
36 |
37 | $document.find('body').append(form);
38 | form[0].submit();
39 | form.remove();
40 | };
41 | })
42 |
43 | .controller('PlunkerCtrl', function ($scope, plunkGenerator) {
44 |
45 | $scope.content = {};
46 |
47 | $scope.edit = function (version, module, plunker) {
48 | plunkGenerator(version, module, $scope.content);
49 | };
50 | })
51 |
52 | .directive('plunkerContent', function () {
53 | return {
54 | link:function (scope, element, attrs) {
55 | scope.$parent.content[attrs.plunkerContent] = element.text();
56 | }
57 | };
58 | });
59 |
--------------------------------------------------------------------------------
/demo/assets/rainbow-generic.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generic language patterns
3 | *
4 | * @author Craig Campbell
5 | * @version 1.0.9
6 | */
7 | Rainbow.extend([
8 | {
9 | 'matches': {
10 | 1: {
11 | 'name': 'keyword.operator',
12 | 'pattern': /\=/g
13 | },
14 | 2: {
15 | 'name': 'string',
16 | 'matches': {
17 | 'name': 'constant.character.escape',
18 | 'pattern': /\\('|"){1}/g
19 | }
20 | }
21 | },
22 | 'pattern': /(\(|\s|\[|\=|:)(('|")([^\\\1]|\\.)*?(\3))/gm
23 | },
24 | {
25 | 'name': 'comment',
26 | 'pattern': /\/\*[\s\S]*?\*\/|(\/\/|\#)[\s\S]*?$/gm
27 | },
28 | {
29 | 'name': 'constant.numeric',
30 | 'pattern': /\b(\d+(\.\d+)?(e(\+|\-)?\d+)?(f|d)?|0x[\da-f]+)\b/gi
31 | },
32 | {
33 | 'matches': {
34 | 1: 'keyword'
35 | },
36 | 'pattern': /\b(and|array|as|bool(ean)?|c(atch|har|lass|onst)|d(ef|elete|o(uble)?)|e(cho|lse(if)?|xit|xtends|xcept)|f(inally|loat|or(each)?|unction)|global|if|import|int(eger)?|long|new|object|or|pr(int|ivate|otected)|public|return|self|st(ring|ruct|atic)|switch|th(en|is|row)|try|(un)?signed|var|void|while)(?=\(|\b)/gi
37 | },
38 | {
39 | 'name': 'constant.language',
40 | 'pattern': /true|false|null/g
41 | },
42 | {
43 | 'name': 'keyword.operator',
44 | 'pattern': /\+|\!|\-|&(gt|lt|amp);|\||\*|\=/g
45 | },
46 | {
47 | 'matches': {
48 | 1: 'function.call'
49 | },
50 | 'pattern': /(\w+?)(?=\()/g
51 | },
52 | {
53 | 'matches': {
54 | 1: 'storage.function',
55 | 2: 'entity.name.function'
56 | },
57 | 'pattern': /(function)\s(.*?)(?=\()/g
58 | }
59 | ]);
60 |
--------------------------------------------------------------------------------
/demo/assets/rainbow-html.js:
--------------------------------------------------------------------------------
1 | /**
2 | * HTML patterns
3 | *
4 | * @author Craig Campbell
5 | * @version 1.0.7
6 | */
7 | Rainbow.extend('html', [
8 | {
9 | 'name': 'source.php.embedded',
10 | 'matches': {
11 | 2: {
12 | 'language': 'php'
13 | }
14 | },
15 | 'pattern': /<\?=?(?!xml)(php)?([\s\S]*?)(\?>)/gm
16 | },
17 | {
18 | 'name': 'source.css.embedded',
19 | 'matches': {
20 | 0: {
21 | 'language': 'css'
22 | }
23 | },
24 | 'pattern': /<style(.*?)>([\s\S]*?)<\/style>/gm
25 | },
26 | {
27 | 'name': 'source.js.embedded',
28 | 'matches': {
29 | 0: {
30 | 'language': 'javascript'
31 | }
32 | },
33 | 'pattern': /<script(?! src)(.*?)>([\s\S]*?)<\/script>/gm
34 | },
35 | {
36 | 'name': 'comment.html',
37 | 'pattern': /<\!--[\S\s]*?-->/g
38 | },
39 | {
40 | 'matches': {
41 | 1: 'support.tag.open',
42 | 2: 'support.tag.cclose'
43 | },
44 | 'pattern': /(<)|(\/?\??>)/g
45 | },
46 | {
47 | 'name': 'support.tag',
48 | 'matches': {
49 | 1: 'support.tag',
50 | 2: 'support.tag.special',
51 | 3: 'support.tag-name'
52 | },
53 | 'pattern': /(<\??)(\/|\!?)(\w+)/g
54 | },
55 | {
56 | 'matches': {
57 | 1: 'support.attribute'
58 | },
59 | 'pattern': /([a-z-]+)(?=\=)/gi
60 | },
61 | {
62 | 'matches': {
63 | 1: 'support.operator',
64 | 2: 'string.quote',
65 | 3: 'string.value',
66 | 4: 'string.quote'
67 | },
68 | 'pattern': /(=)('|")(.*?)(\2)/g
69 | },
70 | {
71 | 'matches': {
72 | 1: 'support.operator',
73 | 2: 'support.value'
74 | },
75 | 'pattern': /(=)([a-zA-Z\-0-9]*)\b/g
76 | },
77 | {
78 | 'matches': {
79 | 1: 'support.attribute'
80 | },
81 | 'pattern': /\s(\w+)(?=\s|>)(?![\s\S]*<)/g
82 | }
83 | ], true);
84 |
--------------------------------------------------------------------------------
/demo/assets/rainbow-javascript.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript patterns
3 | *
4 | * @author Craig Campbell
5 | * @version 1.0.7
6 | */
7 | Rainbow.extend('javascript', [
8 |
9 | /**
10 | * matches $. or $(
11 | */
12 | {
13 | 'name': 'selector',
14 | 'pattern': /(\s|^)\$(?=\.|\()/g
15 | },
16 | {
17 | 'name': 'support',
18 | 'pattern': /\b(window|document)\b/g
19 | },
20 | {
21 | 'matches': {
22 | 1: 'support.property'
23 | },
24 | 'pattern': /\.(length|node(Name|Value))\b/g
25 | },
26 | {
27 | 'matches': {
28 | 1: 'support.function'
29 | },
30 | 'pattern': /(setTimeout|setInterval)(?=\()/g
31 |
32 | },
33 | {
34 | 'matches': {
35 | 1: 'support.method'
36 | },
37 | 'pattern': /\.(getAttribute|push|getElementById|getElementsByClassName|log|setTimeout|setInterval)(?=\()/g
38 | },
39 | {
40 | 'matches': {
41 | 1: 'support.tag.script',
42 | 2: [
43 | {
44 | 'name': 'string',
45 | 'pattern': /('|")(.*?)(\1)/g
46 | },
47 | {
48 | 'name': 'entity.tag.script',
49 | 'pattern': /(\w+)/g
50 | }
51 | ],
52 | 3: 'support.tag.script'
53 | },
54 | 'pattern': /(<\/?)(script.*?)(>)/g
55 | },
56 |
57 | /**
58 | * matches any escaped characters inside of a js regex pattern
59 | *
60 | * @see https://github.com/ccampbell/rainbow/issues/22
61 | *
62 | * this was causing single line comments to fail so it now makes sure
63 | * the opening / is not directly followed by a *
64 | *
65 | * @todo check that there is valid regex in match group 1
66 | */
67 | {
68 | 'name': 'string.regexp',
69 | 'matches': {
70 | 1: 'string.regexp.open',
71 | 2: {
72 | 'name': 'constant.regexp.escape',
73 | 'pattern': /\\(.){1}/g
74 | },
75 | 3: 'string.regexp.cclose',
76 | 4: 'string.regexp.modifier'
77 | },
78 | 'pattern': /(\/)(?!\*)(.+)(\/)([igm]{0,3})/g
79 | },
80 |
81 | /**
82 | * matches runtime function declarations
83 | */
84 | {
85 | 'matches': {
86 | 1: 'storage',
87 | 3: 'entity.function'
88 | },
89 | 'pattern': /(var)?(\s|^)(.*)(?=\s?=\s?function\()/g
90 | },
91 |
92 | /**
93 | * matches constructor call
94 | */
95 | {
96 | 'matches': {
97 | 1: 'keyword',
98 | 2: 'entity.function'
99 | },
100 | 'pattern': /(new)\s+(.*)(?=\()/g
101 | },
102 |
103 | /**
104 | * matches any function call in the style functionName: function()
105 | */
106 | {
107 | 'name': 'entity.function',
108 | 'pattern': /(\w+)(?=:\s{0,}function)/g
109 | }
110 | ]);
111 |
--------------------------------------------------------------------------------
/demo/assets/rainbow.css:
--------------------------------------------------------------------------------
1 | /**
2 | * GitHub theme
3 | *
4 | * @author Craig Campbell
5 | * @version 1.0.4
6 | */
7 | pre {
8 | border: 1px solid #ccc;
9 | word-wrap: break-word;
10 | padding: 6px 10px;
11 | line-height: 19px;
12 | margin-bottom: 20px;
13 | }
14 |
15 | code {
16 | border: 1px solid #eaeaea;
17 | margin: 0px 2px;
18 | padding: 0px 5px;
19 | font-size: 12px;
20 | }
21 |
22 | pre code {
23 | border: 0px;
24 | padding: 0px;
25 | margin: 0px;
26 | -moz-border-radius: 0px;
27 | -webkit-border-radius: 0px;
28 | border-radius: 0px;
29 | }
30 |
31 | pre, code {
32 | font-family: Consolas, 'Liberation Mono', Courier, monospace;
33 | color: #333;
34 | background: #f8f8f8;
35 | -moz-border-radius: 3px;
36 | -webkit-border-radius: 3px;
37 | border-radius: 3px;
38 | }
39 |
40 | pre, pre code {
41 | font-size: 13px;
42 | }
43 |
44 | pre .comment {
45 | color: #998;
46 | }
47 |
48 | pre .support {
49 | color: #0086B3;
50 | }
51 |
52 | pre .tag, pre .tag-name {
53 | color: navy;
54 | }
55 |
56 | pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function {
57 | font-weight: bold;
58 | }
59 |
60 | pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace {
61 | color: #333;
62 | }
63 |
64 | pre .constant.numeric, pre .keyword.unit, pre .hex-color {
65 | font-weight: normal;
66 | color: #099;
67 | }
68 |
69 | pre .entity.class {
70 | color: #458;
71 | }
72 |
73 | pre .entity.id, pre .entity.function {
74 | color: #900;
75 | }
76 |
77 | pre .attribute, pre .variable {
78 | color: teal;
79 | }
80 |
81 | pre .string, pre .support.value {
82 | font-weight: normal;
83 | color: #d14;
84 | }
85 |
86 | pre .regexp {
87 | color: #009926;
88 | }
89 |
--------------------------------------------------------------------------------
/demo/assets/rainbow.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Craig Campbell
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Rainbow is a simple code syntax highlighter
17 | *
18 | * @preserve @version 1.1.8
19 | * @url rainbowco.de
20 | */
21 | window['Rainbow'] = (function() {
22 |
23 | /**
24 | * array of replacements to process at the end
25 | *
26 | * @type {Object}
27 | */
28 | var replacements = {},
29 |
30 | /**
31 | * an array of start and end positions of blocks to be replaced
32 | *
33 | * @type {Object}
34 | */
35 | replacement_positions = {},
36 |
37 | /**
38 | * an array of the language patterns specified for each language
39 | *
40 | * @type {Object}
41 | */
42 | language_patterns = {},
43 |
44 | /**
45 | * an array of languages and whether they should bypass the default patterns
46 | *
47 | * @type {Object}
48 | */
49 | bypass_defaults = {},
50 |
51 | /**
52 | * processing level
53 | *
54 | * replacements are stored at this level so if there is a sub block of code
55 | * (for example php inside of html) it runs at a different level
56 | *
57 | * @type {number}
58 | */
59 | CURRENT_LEVEL = 0,
60 |
61 | /**
62 | * constant used to refer to the default language
63 | *
64 | * @type {number}
65 | */
66 | DEFAULT_LANGUAGE = 0,
67 |
68 | /**
69 | * used as counters so we can selectively call setTimeout
70 | * after processing a certain number of matches/replacements
71 | *
72 | * @type {number}
73 | */
74 | match_counter = 0,
75 |
76 | /**
77 | * @type {number}
78 | */
79 | replacement_counter = 0,
80 |
81 | /**
82 | * @type {null|string}
83 | */
84 | global_class,
85 |
86 | /**
87 | * @type {null|Function}
88 | */
89 | onHighlight;
90 |
91 | /**
92 | * cross browser get attribute for an element
93 | *
94 | * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
95 | *
96 | * @param {Node} el
97 | * @param {string} attr attribute you are trying to get
98 | * @returns {string|number}
99 | */
100 | function _attr(el, attr, attrs, i) {
101 | var result = (el.getAttribute && el.getAttribute(attr)) || 0;
102 |
103 | if (!result) {
104 | attrs = el.attributes;
105 |
106 | for (i = 0; i < attrs.length; ++i) {
107 | if (attrs[i].nodeName === attr) {
108 | return attrs[i].nodeValue;
109 | }
110 | }
111 | }
112 |
113 | return result;
114 | }
115 |
116 | /**
117 | * adds a class to a given code block
118 | *
119 | * @param {Element} el
120 | * @param {string} class_name class name to add
121 | * @returns void
122 | */
123 | function _addClass(el, class_name) {
124 | el.className += el.className ? ' ' + class_name : class_name;
125 | }
126 |
127 | /**
128 | * checks if a block has a given class
129 | *
130 | * @param {Element} el
131 | * @param {string} class_name class name to check for
132 | * @returns {boolean}
133 | */
134 | function _hasClass(el, class_name) {
135 | return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1;
136 | }
137 |
138 | /**
139 | * gets the language for this block of code
140 | *
141 | * @param {Element} block
142 | * @returns {string|null}
143 | */
144 | function _getLanguageForBlock(block) {
145 |
146 | // if this doesn't have a language but the parent does then use that
147 | // this means if for example you have:
148 | // with a bunch of blocks inside then you do not have
149 | // to specify the language for each block
150 | var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');
151 |
152 | // this adds support for specifying language via a css class
153 | // you can use the Google Code Prettify style:
154 | // or the HTML5 style:
155 | if (!language) {
156 | var pattern = /\blang(?:uage)?-(\w+)/,
157 | match = block.className.match(pattern) || block.parentNode.className.match(pattern);
158 |
159 | if (match) {
160 | language = match[1];
161 | }
162 | }
163 |
164 | return language;
165 | }
166 |
167 | /**
168 | * makes sure html entities are always used for tags
169 | *
170 | * @param {string} code
171 | * @returns {string}
172 | */
173 | function _htmlEntities(code) {
174 | return code.replace(//g, '>').replace(/&(?![\w\#]+;)/g, '&');
175 | }
176 |
177 | /**
178 | * determines if a new match intersects with an existing one
179 | *
180 | * @param {number} start1 start position of existing match
181 | * @param {number} end1 end position of existing match
182 | * @param {number} start2 start position of new match
183 | * @param {number} end2 end position of new match
184 | * @returns {boolean}
185 | */
186 | function _intersects(start1, end1, start2, end2) {
187 | if (start2 >= start1 && start2 < end1) {
188 | return true;
189 | }
190 |
191 | return end2 > start1 && end2 < end1;
192 | }
193 |
194 | /**
195 | * determines if two different matches have complete overlap with each other
196 | *
197 | * @param {number} start1 start position of existing match
198 | * @param {number} end1 end position of existing match
199 | * @param {number} start2 start position of new match
200 | * @param {number} end2 end position of new match
201 | * @returns {boolean}
202 | */
203 | function _hasCompleteOverlap(start1, end1, start2, end2) {
204 |
205 | // if the starting and end positions are exactly the same
206 | // then the first one should stay and this one should be ignored
207 | if (start2 == start1 && end2 == end1) {
208 | return false;
209 | }
210 |
211 | return start2 <= start1 && end2 >= end1;
212 | }
213 |
214 | /**
215 | * determines if the match passed in falls inside of an existing match
216 | * this prevents a regex pattern from matching inside of a bigger pattern
217 | *
218 | * @param {number} start - start position of new match
219 | * @param {number} end - end position of new match
220 | * @returns {boolean}
221 | */
222 | function _matchIsInsideOtherMatch(start, end) {
223 | for (var key in replacement_positions[CURRENT_LEVEL]) {
224 | key = parseInt(key, 10);
225 |
226 | // if this block completely overlaps with another block
227 | // then we should remove the other block and return false
228 | if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
229 | delete replacement_positions[CURRENT_LEVEL][key];
230 | delete replacements[CURRENT_LEVEL][key];
231 | }
232 |
233 | if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
234 | return true;
235 | }
236 | }
237 |
238 | return false;
239 | }
240 |
241 | /**
242 | * takes a string of code and wraps it in a span tag based on the name
243 | *
244 | * @param {string} name name of the pattern (ie keyword.regex)
245 | * @param {string} code block of code to wrap
246 | * @returns {string}
247 | */
248 | function _wrapCodeInSpan(name, code) {
249 | return '' + code + '';
250 | }
251 |
252 | /**
253 | * finds out the position of group match for a regular expression
254 | *
255 | * @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match
256 | *
257 | * @param {Object} match
258 | * @param {number} group_number
259 | * @returns {number}
260 | */
261 | function _indexOfGroup(match, group_number) {
262 | var index = 0,
263 | i;
264 |
265 | for (i = 1; i < group_number; ++i) {
266 | if (match[i]) {
267 | index += match[i].length;
268 | }
269 | }
270 |
271 | return index;
272 | }
273 |
274 | /**
275 | * matches a regex pattern against a block of code
276 | * finds all matches that should be processed and stores the positions
277 | * of where they should be replaced within the string
278 | *
279 | * this is where pretty much all the work is done but it should not
280 | * be called directly
281 | *
282 | * @param {RegExp} pattern
283 | * @param {string} code
284 | * @returns void
285 | */
286 | function _processPattern(regex, pattern, code, callback)
287 | {
288 | var match = regex.exec(code);
289 |
290 | if (!match) {
291 | return callback();
292 | }
293 |
294 | ++match_counter;
295 |
296 | // treat match 0 the same way as name
297 | if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {
298 | pattern['name'] = pattern['matches'][0];
299 | delete pattern['matches'][0];
300 | }
301 |
302 | var replacement = match[0],
303 | start_pos = match.index,
304 | end_pos = match[0].length + start_pos,
305 |
306 | /**
307 | * callback to process the next match of this pattern
308 | */
309 | processNext = function() {
310 | var nextCall = function() {
311 | _processPattern(regex, pattern, code, callback);
312 | };
313 |
314 | // every 100 items we process let's call set timeout
315 | // to let the ui breathe a little
316 | return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
317 | };
318 |
319 | // if this is not a child match and it falls inside of another
320 | // match that already happened we should skip it and continue processing
321 | if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
322 | return processNext();
323 | }
324 |
325 | /**
326 | * callback for when a match was successfully processed
327 | *
328 | * @param {string} replacement
329 | * @returns void
330 | */
331 | var onMatchSuccess = function(replacement) {
332 | // if this match has a name then wrap it in a span tag
333 | if (pattern['name']) {
334 | replacement = _wrapCodeInSpan(pattern['name'], replacement);
335 | }
336 |
337 | // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);
338 |
339 | // store what needs to be replaced with what at this position
340 | if (!replacements[CURRENT_LEVEL]) {
341 | replacements[CURRENT_LEVEL] = {};
342 | replacement_positions[CURRENT_LEVEL] = {};
343 | }
344 |
345 | replacements[CURRENT_LEVEL][start_pos] = {
346 | 'replace': match[0],
347 | 'with': replacement
348 | };
349 |
350 | // store the range of this match so we can use it for comparisons
351 | // with other matches later
352 | replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;
353 |
354 | // process the next match
355 | processNext();
356 | },
357 |
358 | // if this pattern has sub matches for different groups in the regex
359 | // then we should process them one at a time by rerunning them through
360 | // this function to generate the new replacement
361 | //
362 | // we run through them backwards because the match position of earlier
363 | // matches will not change depending on what gets replaced in later
364 | // matches
365 | group_keys = keys(pattern['matches']),
366 |
367 | /**
368 | * callback for processing a sub group
369 | *
370 | * @param {number} i
371 | * @param {Array} group_keys
372 | * @param {Function} callback
373 | */
374 | processGroup = function(i, group_keys, callback) {
375 | if (i >= group_keys.length) {
376 | return callback(replacement);
377 | }
378 |
379 | var processNextGroup = function() {
380 | processGroup(++i, group_keys, callback);
381 | },
382 | block = match[group_keys[i]];
383 |
384 | // if there is no match here then move on
385 | if (!block) {
386 | return processNextGroup();
387 | }
388 |
389 | var group = pattern['matches'][group_keys[i]],
390 | language = group['language'],
391 |
392 | /**
393 | * process group is what group we should use to actually process
394 | * this match group
395 | *
396 | * for example if the subgroup pattern looks like this
397 | * 2: {
398 | * 'name': 'keyword',
399 | * 'pattern': /true/g
400 | * }
401 | *
402 | * then we use that as is, but if it looks like this
403 | *
404 | * 2: {
405 | * 'name': 'keyword',
406 | * 'matches': {
407 | * 'name': 'special',
408 | * 'pattern': /whatever/g
409 | * }
410 | * }
411 | *
412 | * we treat the 'matches' part as the pattern and keep
413 | * the name around to wrap it with later
414 | */
415 | process_group = group['name'] && group['matches'] ? group['matches'] : group,
416 |
417 | /**
418 | * takes the code block matched at this group, replaces it
419 | * with the highlighted block, and optionally wraps it with
420 | * a span with a name
421 | *
422 | * @param {string} block
423 | * @param {string} replace_block
424 | * @param {string|null} match_name
425 | */
426 | _replaceAndContinue = function(block, replace_block, match_name) {
427 | replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);
428 | processNextGroup();
429 | };
430 |
431 | // if this is a sublanguage go and process the block using that language
432 | if (language) {
433 | return _highlightBlockForLanguage(block, language, function(code) {
434 | _replaceAndContinue(block, code);
435 | });
436 | }
437 |
438 | // if this is a string then this match is directly mapped to selector
439 | // so all we have to do is wrap it in a span and continue
440 | if (typeof group === 'string') {
441 | return _replaceAndContinue(block, block, group);
442 | }
443 |
444 | // the process group can be a single pattern or an array of patterns
445 | // _processCodeWithPatterns always expects an array so we convert it here
446 | _processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {
447 | _replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);
448 | });
449 | };
450 |
451 | processGroup(0, group_keys, onMatchSuccess);
452 | }
453 |
454 | /**
455 | * should a language bypass the default patterns?
456 | *
457 | * if you call Rainbow.extend() and pass true as the third argument
458 | * it will bypass the defaults
459 | */
460 | function _bypassDefaultPatterns(language)
461 | {
462 | return bypass_defaults[language];
463 | }
464 |
465 | /**
466 | * returns a list of regex patterns for this language
467 | *
468 | * @param {string} language
469 | * @returns {Array}
470 | */
471 | function _getPatternsForLanguage(language) {
472 | var patterns = language_patterns[language] || [],
473 | default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];
474 |
475 | return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);
476 | }
477 |
478 | /**
479 | * substring replace call to replace part of a string at a certain position
480 | *
481 | * @param {number} position the position where the replacement should happen
482 | * @param {string} replace the text we want to replace
483 | * @param {string} replace_with the text we want to replace it with
484 | * @param {string} code the code we are doing the replacing in
485 | * @returns {string}
486 | */
487 | function _replaceAtPosition(position, replace, replace_with, code) {
488 | var sub_string = code.substr(position);
489 | return code.substr(0, position) + sub_string.replace(replace, replace_with);
490 | }
491 |
492 | /**
493 | * sorts an object by index descending
494 | *
495 | * @param {Object} object
496 | * @return {Array}
497 | */
498 | function keys(object) {
499 | var locations = [],
500 | replacement,
501 | pos;
502 |
503 | for(var location in object) {
504 | if (object.hasOwnProperty(location)) {
505 | locations.push(location);
506 | }
507 | }
508 |
509 | // numeric descending
510 | return locations.sort(function(a, b) {
511 | return b - a;
512 | });
513 | }
514 |
515 | /**
516 | * processes a block of code using specified patterns
517 | *
518 | * @param {string} code
519 | * @param {Array} patterns
520 | * @returns void
521 | */
522 | function _processCodeWithPatterns(code, patterns, callback)
523 | {
524 | // we have to increase the level here so that the
525 | // replacements will not conflict with each other when
526 | // processing sub blocks of code
527 | ++CURRENT_LEVEL;
528 |
529 | // patterns are processed one at a time through this function
530 | function _workOnPatterns(patterns, i)
531 | {
532 | // still have patterns to process, keep going
533 | if (i < patterns.length) {
534 | return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
535 | _workOnPatterns(patterns, ++i);
536 | });
537 | }
538 |
539 | // we are done processing the patterns
540 | // process the replacements and update the DOM
541 | _processReplacements(code, function(code) {
542 |
543 | // when we are done processing replacements
544 | // we are done at this level so we can go back down
545 | delete replacements[CURRENT_LEVEL];
546 | delete replacement_positions[CURRENT_LEVEL];
547 | --CURRENT_LEVEL;
548 | callback(code);
549 | });
550 | }
551 |
552 | _workOnPatterns(patterns, 0);
553 | }
554 |
555 | /**
556 | * process replacements in the string of code to actually update the markup
557 | *
558 | * @param {string} code the code to process replacements in
559 | * @param {Function} onComplete what to do when we are done processing
560 | * @returns void
561 | */
562 | function _processReplacements(code, onComplete) {
563 |
564 | /**
565 | * processes a single replacement
566 | *
567 | * @param {string} code
568 | * @param {Array} positions
569 | * @param {number} i
570 | * @param {Function} onComplete
571 | * @returns void
572 | */
573 | function _processReplacement(code, positions, i, onComplete) {
574 | if (i < positions.length) {
575 | ++replacement_counter;
576 | var pos = positions[i],
577 | replacement = replacements[CURRENT_LEVEL][pos];
578 | code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
579 |
580 | // process next function
581 | var next = function() {
582 | _processReplacement(code, positions, ++i, onComplete);
583 | };
584 |
585 | // use a timeout every 250 to not freeze up the UI
586 | return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);
587 | }
588 |
589 | onComplete(code);
590 | }
591 |
592 | var string_positions = keys(replacements[CURRENT_LEVEL]);
593 | _processReplacement(code, string_positions, 0, onComplete);
594 | }
595 |
596 | /**
597 | * takes a string of code and highlights it according to the language specified
598 | *
599 | * @param {string} code
600 | * @param {string} language
601 | * @param {Function} onComplete
602 | * @returns void
603 | */
604 | function _highlightBlockForLanguage(code, language, onComplete) {
605 | var patterns = _getPatternsForLanguage(language);
606 | _processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);
607 | }
608 |
609 | /**
610 | * highlight an individual code block
611 | *
612 | * @param {Array} code_blocks
613 | * @param {number} i
614 | * @returns void
615 | */
616 | function _highlightCodeBlock(code_blocks, i, onComplete) {
617 | if (i < code_blocks.length) {
618 | var block = code_blocks[i],
619 | language = _getLanguageForBlock(block);
620 |
621 | if (!_hasClass(block, 'rainbow') && language) {
622 | language = language.toLowerCase();
623 |
624 | _addClass(block, 'rainbow');
625 |
626 | return _highlightBlockForLanguage(block.innerHTML, language, function(code) {
627 | block.innerHTML = code;
628 |
629 | // reset the replacement arrays
630 | replacements = {};
631 | replacement_positions = {};
632 |
633 | // if you have a listener attached tell it that this block is now highlighted
634 | if (onHighlight) {
635 | onHighlight(block, language);
636 | }
637 |
638 | // process the next block
639 | setTimeout(function() {
640 | _highlightCodeBlock(code_blocks, ++i, onComplete);
641 | }, 0);
642 | });
643 | }
644 | return _highlightCodeBlock(code_blocks, ++i, onComplete);
645 | }
646 |
647 | if (onComplete) {
648 | onComplete();
649 | }
650 | }
651 |
652 | /**
653 | * start highlighting all the code blocks
654 | *
655 | * @returns void
656 | */
657 | function _highlight(node, onComplete) {
658 |
659 | // the first argument can be an Event or a DOM Element
660 | // I was originally checking instanceof Event but that makes it break
661 | // when using mootools
662 | //
663 | // @see https://github.com/ccampbell/rainbow/issues/32
664 | //
665 | node = node && typeof node.getElementsByTagName == 'function' ? node : document;
666 |
667 | var pre_blocks = node.getElementsByTagName('pre'),
668 | code_blocks = node.getElementsByTagName('code'),
669 | i,
670 | final_blocks = [];
671 |
672 | // @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript
673 | // we are going to process all blocks
674 | for (i = 0; i < code_blocks.length; ++i) {
675 | final_blocks.push(code_blocks[i]);
676 | }
677 |
678 | // loop through the pre blocks to see which ones we should add
679 | for (i = 0; i < pre_blocks.length; ++i) {
680 |
681 | // if the pre block has no code blocks then process it directly
682 | if (!pre_blocks[i].getElementsByTagName('code').length) {
683 | final_blocks.push(pre_blocks[i]);
684 | }
685 | }
686 |
687 | _highlightCodeBlock(final_blocks, 0, onComplete);
688 | }
689 |
690 | /**
691 | * public methods
692 | */
693 | return {
694 |
695 | /**
696 | * extends the language pattern matches
697 | *
698 | * @param {*} language name of language
699 | * @param {*} patterns array of patterns to add on
700 | * @param {boolean|null} bypass if true this will bypass the default language patterns
701 | */
702 | extend: function(language, patterns, bypass) {
703 |
704 | // if there is only one argument then we assume that we want to
705 | // extend the default language rules
706 | if (arguments.length == 1) {
707 | patterns = language;
708 | language = DEFAULT_LANGUAGE;
709 | }
710 |
711 | bypass_defaults[language] = bypass;
712 | language_patterns[language] = patterns.concat(language_patterns[language] || []);
713 | },
714 |
715 | /**
716 | * call back to let you do stuff in your app after a piece of code has been highlighted
717 | *
718 | * @param {Function} callback
719 | */
720 | onHighlight: function(callback) {
721 | onHighlight = callback;
722 | },
723 |
724 | /**
725 | * method to set a global class that will be applied to all spans
726 | *
727 | * @param {string} class_name
728 | */
729 | addClass: function(class_name) {
730 | global_class = class_name;
731 | },
732 |
733 | /**
734 | * starts the magic rainbow
735 | *
736 | * @returns void
737 | */
738 | color: function() {
739 |
740 | // if you want to straight up highlight a string you can pass the string of code,
741 | // the language, and a callback function
742 | if (typeof arguments[0] == 'string') {
743 | return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]);
744 | }
745 |
746 | // if you pass a callback function then we rerun the color function
747 | // on all the code and call the callback function on complete
748 | if (typeof arguments[0] == 'function') {
749 | return _highlight(0, arguments[0]);
750 | }
751 |
752 | // otherwise we use whatever node you passed in with an optional
753 | // callback function as the second parameter
754 | _highlight(arguments[0], arguments[1]);
755 | }
756 | };
757 | }) ();
758 |
759 | /**
760 | * adds event listener to start highlighting
761 | */
762 | (function() {
763 | if (window.addEventListener) {
764 | return window.addEventListener('load', Rainbow.color, false);
765 | }
766 | window.attachEvent('onload', Rainbow.color);
767 | }) ();
768 |
769 | // When using Google closure compiler in advanced mode some methods
770 | // get renamed. This keeps a public reference to these methods so they can
771 | // still be referenced from outside this library.
772 | Rainbow["onHighlight"] = Rainbow.onHighlight;
773 | Rainbow["addClass"] = Rainbow.addClass;
774 |
--------------------------------------------------------------------------------
/demo/assets/ui-bootstrap-tpls-0.1.0-SNAPSHOT.min.js:
--------------------------------------------------------------------------------
1 | angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.carousel","ui.bootstrap.collapse","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.transition"]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]),angular.module("ui.bootstrap.accordion").controller("AccordionController",["$scope","$attrs",function(e,t){this.groups=[],this.closeOthers=function(n){(angular.isUndefined(t.closeOthers)||e.$eval(t.closeOthers))&&angular.forEach(this.groups,function(e){e!==n&&(e.isOpen=!1)})},this.addGroup=function(e){var t=this;this.groups.push(e),e.$on("$destroy",function(n){t.removeGroup(e)})},this.removeGroup=function(e){var t=this.groups.indexOf(e);t!==-1&&this.groups.splice(this.groups.indexOf(e),1)}}]),angular.module("ui.bootstrap.accordion").directive("accordion",function(){return{restrict:"E",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}),angular.module("ui.bootstrap.accordion").directive("accordionGroup",["$parse","$transition","$timeout",function(e,t,n){return{require:"^accordion",restrict:"E",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},link:function(t,n,r,i){var s,o;i.addGroup(t),t.isOpen=!1,r.isOpen&&(s=e(r.isOpen),o=s.assign,t.$watch(function(){return s(t.$parent)},function(n){t.isOpen=n}),t.isOpen=s?s(t.$parent):!1),t.$watch("isOpen",function(e){e&&i.closeOthers(t),o&&o(t.$parent,e)})}}}]),angular.module("ui.bootstrap.alert",[]).directive("alert",function(){return{restrict:"E",templateUrl:"template/alert/alert.html",transclude:!0,scope:{type:"=",close:"&"},link:function(e,t,n){e.type=e.type||"info",e.dismiss=function(){e.close()}}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition","$q",function(e,t,n,r){function f(){function n(){a?(e.next(),f()):e.pause()}u&&t.cancel(u);var r=+e.interval;!isNaN(r)&&r>=0&&(u=t(n,r))}var i=this,s=i.slides=[],o=-1,u,a;i.currentSlide=null,i.select=function(r,u){function l(){i.currentSlide&&angular.isString(u)&&!e.noTransition&&r.$element?(r.$element.addClass(u),r.$element[0].offsetWidth=r.$element[0].offsetWidth,angular.forEach(s,function(e){angular.extend(e,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(r,{direction:u,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:u,leaving:!0}),e.$currentTransition=n(r.$element,{}),function(t,n){e.$currentTransition.then(function(){c(t,n)},function(){c(t,n)})}(r,i.currentSlide)):c(r,i.currentSlide),i.currentSlide=r,o=a,f()}function c(t,n){angular.extend(t,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(n||{},{direction:"",active:!1,leaving:!1,entering:!1}),e.$currentTransition=null}var a=s.indexOf(r);u===undefined&&(u=a>o?"next":"prev"),r&&r!==i.currentSlide&&(e.$currentTransition?(e.$currentTransition.cancel(),t(l)):l())},i.indexOfSlide=function(e){return s.indexOf(e)},e.next=function(){var e=(o+1)%s.length;return i.select(s[e],"next")},e.prev=function(){var e=o-1<0?s.length-1:o-1;return i.select(s[e],"prev")},e.$watch("interval",f),e.play=function(){a||(a=!0,f())},e.pause=function(){a=!1,u&&t.cancel(u)},i.addSlide=function(t,n){t.$element=n,s.push(t),s.length===1||t.active?(i.select(s[s.length-1]),s.length==1&&e.play()):t.active=!1},i.removeSlide=function(e){var t=s.indexOf(e);s.splice(t,1),s.length>0&&e.active&&(t>=s.length?i.select(s[t-1]):i.select(s[t]))}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"="}}}]).directive("slide",[function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"="},link:function(e,t,n,r){r.addSlide(e,t),e.$on("$destroy",function(){r.removeSlide(e)}),e.$watch("active",function(t){t&&r.select(e)})}}}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(e){var t=function(e,t,n){t.removeClass("collapse"),t.css({height:n});var r=t[0].offsetWidth;t.addClass("collapse")};return{link:function(n,r,i){var s;n.$watch(i.collapse,function(e){e?f():a()});var o,u=function(t){return o&&o.cancel(),o=e(r,t),o.then(function(){o=undefined},function(){o=undefined}),o},a=function(){u({height:r[0].scrollHeight+"px"}).then(function(){s||t(n,r,"auto")}),s=!1},f=function(){s=!0,t(n,r,r[0].scrollHeight+"px"),u({height:"0"})}}}}]);var dialogModule=angular.module("ui.bootstrap.dialog",["ui.bootstrap.transition"]);dialogModule.controller("MessageBoxController",["$scope","dialog","model",function(e,t,n){e.title=n.title,e.message=n.message,e.buttons=n.buttons,e.close=function(e){t.close(e)}}]),dialogModule.provider("$dialog",function(){var e={backdrop:!0,modalClass:"modal",backdropClass:"modal-backdrop",transitionClass:"fade",triggerClass:"in",resolve:{},backdropFade:!1,modalFade:!1,keyboard:!0,backdropClick:!0},t={};this.options=function(e){t=e},this.$get=["$http","$document","$compile","$rootScope","$controller","$templateCache","$q","$transition",function(n,r,i,s,o,u,a,f){function c(e){var t=angular.element("
");return t.addClass(e),t}function h(n){var r=this,i=this.options=angular.extend({},e,t,n);this.backdropEl=c(i.backdropClass),i.backdropFade&&(this.backdropEl.addClass(i.transitionClass),this.backdropEl.removeClass(i.triggerClass)),this.modalEl=c(i.modalClass),i.modalFade&&(this.modalEl.addClass(i.transitionClass),this.modalEl.removeClass(i.triggerClass)),this.handledEscapeKey=function(e){e.which===27&&(r.close(),e.preventDefault(),r.$scope.$apply())},this.handleBackDropClick=function(e){r.close(),e.preventDefault(),r.$scope.$apply()}}var l=r.find("body");return h.prototype.isOpen=function(){return this._open},h.prototype.open=function(e,t){var n=this,r=this.options;e&&(r.templateUrl=e),t&&(r.controller=t);if(!r.template&&!r.templateUrl)throw new Error("Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.");return this._loadResolves().then(function(e){var t=e.$scope=n.$scope=s.$new();n.modalEl.html(e.$template);if(n.options.controller){var r=o(n.options.controller,e);n.modalEl.contents().data("ngControllerController",r)}i(n.modalEl.contents())(t),n._addElementsToDom(),setTimeout(function(){n.options.modalFade&&n.modalEl.addClass(n.options.triggerClass),n.options.backdropFade&&n.backdropEl.addClass(n.options.triggerClass)}),n._bindEvents()}),this.deferred=a.defer(),this.deferred.promise},h.prototype.close=function(e){function i(e){e.removeClass(t.options.triggerClass)}function s(){t._open&&t._onCloseComplete(e)}var t=this,n=this._getFadingElements();if(n.length>0){for(var r=n.length-1;r>=0;r--)f(n[r],i).then(s);return}this._onCloseComplete(e)},h.prototype._getFadingElements=function(){var e=[];return this.options.modalFade&&e.push(this.modalEl),this.options.backdropFade&&e.push(this.backdropEl),e},h.prototype._bindEvents=function(){this.options.keyboard&&l.bind("keydown",this.handledEscapeKey),this.options.backdrop&&this.options.backdropClick&&this.backdropEl.bind("click",this.handleBackDropClick)},h.prototype._unbindEvents=function(){this.options.keyboard&&l.unbind("keydown",this.handledEscapeKey),this.options.backdrop&&this.options.backdropClick&&this.backdropEl.unbind("click",this.handleBackDropClick)},h.prototype._onCloseComplete=function(e){this._removeElementsFromDom(),this._unbindEvents(),this.deferred.resolve(e)},h.prototype._addElementsToDom=function(){l.append(this.modalEl),this.options.backdrop&&l.append(this.backdropEl),this._open=!0},h.prototype._removeElementsFromDom=function(){this.modalEl.remove(),this.options.backdrop&&this.backdropEl.remove(),this._open=!1},h.prototype._loadResolves=function(){var e=[],t=[],r,i=this;return this.options.template?r=a.when(this.options.template):this.options.templateUrl&&(r=n.get(this.options.templateUrl,{cache:u}).then(function(e){return e.data})),angular.forEach(this.options.resolve||[],function(n,r){t.push(r),e.push(n)}),t.push("$template"),e.push(r),a.all(e).then(function(e){var n={};return angular.forEach(e,function(e,r){n[t[r]]=e}),n.dialog=i,n})},{dialog:function(e){return new h(e)},messageBox:function(e,t,n){return new h({templateUrl:"template/dialog/message.html",controller:"MessageBoxController",resolve:{model:{title:e,message:t,buttons:n}}})}}}]}),angular.module("ui.bootstrap.dropdownToggle",[]).directive("dropdownToggle",["$document","$location","$window",function(e,t,n){var r=null,i;return{restrict:"CA",link:function(n,s,o){n.$watch(function(){return t.path()},function(){i&&i()}),s.parent().bind("click",function(e){i&&i()}),s.bind("click",function(t){t.preventDefault(),t.stopPropagation();var n=!1;r&&(n=r===s,i()),n||(s.parent().addClass("open"),r=s,i=function(t){t&&(t.preventDefault(),t.stopPropagation()),e.unbind("click",i),s.parent().removeClass("open"),i=null,r=null},e.bind("click",i))})}}}]),angular.module("ui.bootstrap.modal",[]).directive("modal",["$parse",function(e){var t,n=angular.element(document.getElementsByTagName("body")[0]),r={backdrop:!0,escape:!0};return{restrict:"EA",link:function(i,s,o){function l(e){i.$apply(function(){model.assign(i,e)})}function c(e){e.which===27&&f()}function h(){f()}function p(){u.escape&&n.unbind("keyup",c),u.backdrop&&(t.css("display","none").removeClass("in"),t.unbind("click",h)),s.css("display","none").removeClass("in"),n.removeClass("modal-open")}function d(){u.escape&&n.bind("keyup",c),u.backdrop&&(t.css("display","block").addClass("in"),t.bind("click",h)),s.css("display","block").addClass("in"),n.addClass("modal-open")}var u=angular.extend(r,i.$eval(o.uiOptions||o.bsOptions||o.options)),a=o.modal||o.show,f;o.close?f=function(){i.$apply(o.close)}:f=function(){i.$apply(function(){e(a).assign(i,!1)})},s.addClass("modal"),u.backdrop&&!t&&(t=angular.element(''),t.css("display","none"),n.append(t)),i.$watch(a,function(e,t){e?d():p()})}}}]),angular.module("ui.bootstrap.pagination",[]).directive("pagination",function(){return{restrict:"E",scope:{numPages:"=",currentPage:"=",maxSize:"=",onSelectPage:"&"},templateUrl:"template/pagination/pagination.html",replace:!0,link:function(e){e.$watch("numPages + currentPage + maxSize",function(){e.pages=[],angular.isDefined(e.maxSize)&&e.maxSize>e.numPages&&(e.maxSize=e.numPages);var t=e.maxSize?e.maxSize:e.numPages,n=e.currentPage-Math.floor(t/2);n<1&&(n=1),n+t-1>e.numPages&&(n-=n+t-1-e.numPages);for(var r=0;re.numPages&&e.selectPage(e.numPages)}),e.noPrevious=function(){return e.currentPage===1},e.noNext=function(){return e.currentPage===e.numPages},e.isActive=function(t){return e.currentPage===t},e.selectPage=function(t){e.isActive(t)||(e.currentPage=t,e.onSelectPage({page:t}))},e.selectPrevious=function(){e.noPrevious()||e.selectPage(e.currentPage-1)},e.selectNext=function(){e.noNext()||e.selectPage(e.currentPage+1)}}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsController",["$scope","$element",function(e,t){var n=e.panes=[];e.select=function(t){angular.forEach(n,function(e){e.selected=!1}),t.selected=!0},this.addPane=function(r){n.length||e.select(r),n.push(r)},this.removePane=function(r){var i=n.indexOf(r);n.splice(i,1),r.selected&&n.length>0&&e.select(n[i
437 | *
438 | */
439 | angular.mock.TzDate = function (offset, timestamp) {
440 | var self = new Date(0);
441 | if (angular.isString(timestamp)) {
442 | var tsStr = timestamp;
443 |
444 | self.origDate = jsonStringToDate(timestamp);
445 |
446 | timestamp = self.origDate.getTime();
447 | if (isNaN(timestamp))
448 | throw {
449 | name: "Illegal Argument",
450 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
451 | };
452 | } else {
453 | self.origDate = new Date(timestamp);
454 | }
455 |
456 | var localOffset = new Date(timestamp).getTimezoneOffset();
457 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
458 | self.date = new Date(timestamp + self.offsetDiff);
459 |
460 | self.getTime = function() {
461 | return self.date.getTime() - self.offsetDiff;
462 | };
463 |
464 | self.toLocaleDateString = function() {
465 | return self.date.toLocaleDateString();
466 | };
467 |
468 | self.getFullYear = function() {
469 | return self.date.getFullYear();
470 | };
471 |
472 | self.getMonth = function() {
473 | return self.date.getMonth();
474 | };
475 |
476 | self.getDate = function() {
477 | return self.date.getDate();
478 | };
479 |
480 | self.getHours = function() {
481 | return self.date.getHours();
482 | };
483 |
484 | self.getMinutes = function() {
485 | return self.date.getMinutes();
486 | };
487 |
488 | self.getSeconds = function() {
489 | return self.date.getSeconds();
490 | };
491 |
492 | self.getTimezoneOffset = function() {
493 | return offset * 60;
494 | };
495 |
496 | self.getUTCFullYear = function() {
497 | return self.origDate.getUTCFullYear();
498 | };
499 |
500 | self.getUTCMonth = function() {
501 | return self.origDate.getUTCMonth();
502 | };
503 |
504 | self.getUTCDate = function() {
505 | return self.origDate.getUTCDate();
506 | };
507 |
508 | self.getUTCHours = function() {
509 | return self.origDate.getUTCHours();
510 | };
511 |
512 | self.getUTCMinutes = function() {
513 | return self.origDate.getUTCMinutes();
514 | };
515 |
516 | self.getUTCSeconds = function() {
517 | return self.origDate.getUTCSeconds();
518 | };
519 |
520 | self.getUTCMilliseconds = function() {
521 | return self.origDate.getUTCMilliseconds();
522 | };
523 |
524 | self.getDay = function() {
525 | return self.date.getDay();
526 | };
527 |
528 | // provide this method only on browsers that already have it
529 | if (self.toISOString) {
530 | self.toISOString = function() {
531 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
532 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
533 | padNumber(self.origDate.getUTCDate(), 2) + 'T' +
534 | padNumber(self.origDate.getUTCHours(), 2) + ':' +
535 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
536 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
537 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'
538 | }
539 | }
540 |
541 | //hide all methods not implemented in this mock that the Date prototype exposes
542 | var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
543 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
544 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
545 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
546 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
547 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
548 |
549 | angular.forEach(unimplementedMethods, function(methodName) {
550 | self[methodName] = function() {
551 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock");
552 | };
553 | });
554 |
555 | return self;
556 | };
557 |
558 | //make "tzDateInstance instanceof Date" return true
559 | angular.mock.TzDate.prototype = Date.prototype;
560 | })();
561 |
562 |
563 | /**
564 | * @ngdoc function
565 | * @name angular.mock.debug
566 | * @description
567 | *
568 | * *NOTE*: this is not an injectable instance, just a globally available function.
569 | *
570 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging.
571 | *
572 | * This method is also available on window, where it can be used to display objects on debug console.
573 | *
574 | * @param {*} object - any object to turn into string.
575 | * @return {string} a serialized string of the argument
576 | */
577 | angular.mock.dump = function(object) {
578 | return serialize(object);
579 |
580 | function serialize(object) {
581 | var out;
582 |
583 | if (angular.isElement(object)) {
584 | object = angular.element(object);
585 | out = angular.element('');
586 | angular.forEach(object, function(element) {
587 | out.append(angular.element(element).clone());
588 | });
589 | out = out.html();
590 | } else if (angular.isArray(object)) {
591 | out = [];
592 | angular.forEach(object, function(o) {
593 | out.push(serialize(o));
594 | });
595 | out = '[ ' + out.join(', ') + ' ]';
596 | } else if (angular.isObject(object)) {
597 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
598 | out = serializeScope(object);
599 | } else if (object instanceof Error) {
600 | out = object.stack || ('' + object.name + ': ' + object.message);
601 | } else {
602 | out = angular.toJson(object, true);
603 | }
604 | } else {
605 | out = String(object);
606 | }
607 |
608 | return out;
609 | }
610 |
611 | function serializeScope(scope, offset) {
612 | offset = offset || ' ';
613 | var log = [offset + 'Scope(' + scope.$id + '): {'];
614 | for ( var key in scope ) {
615 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) {
616 | log.push(' ' + key + ': ' + angular.toJson(scope[key]));
617 | }
618 | }
619 | var child = scope.$$childHead;
620 | while(child) {
621 | log.push(serializeScope(child, offset + ' '));
622 | child = child.$$nextSibling;
623 | }
624 | log.push('}');
625 | return log.join('\n' + offset);
626 | }
627 | };
628 |
629 | /**
630 | * @ngdoc object
631 | * @name ngMock.$httpBackend
632 | * @description
633 | * Fake HTTP backend implementation suitable for unit testing application that use the
634 | * {@link ng.$http $http service}.
635 | *
636 | * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less
637 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
638 | *
639 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so
640 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or
641 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is
642 | * to verify whether a certain request has been sent or not, or alternatively just let the
643 | * application make requests, respond with pre-trained responses and assert that the end result is
644 | * what we expect it to be.
645 | *
646 | * This mock implementation can be used to respond with static or dynamic responses via the
647 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
648 | *
649 | * When an Angular application needs some data from a server, it calls the $http service, which
650 | * sends the request to a real server using $httpBackend service. With dependency injection, it is
651 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
652 | * the requests and respond with some testing data without sending a request to real server.
653 | *
654 | * There are two ways to specify what test data should be returned as http responses by the mock
655 | * backend when the code under test makes http requests:
656 | *
657 | * - `$httpBackend.expect` - specifies a request expectation
658 | * - `$httpBackend.when` - specifies a backend definition
659 | *
660 | *
661 | * # Request Expectations vs Backend Definitions
662 | *
663 | * Request expectations provide a way to make assertions about requests made by the application and
664 | * to define responses for those requests. The test will fail if the expected requests are not made
665 | * or they are made in the wrong order.
666 | *
667 | * Backend definitions allow you to define a fake backend for your application which doesn't assert
668 | * if a particular request was made or not, it just returns a trained response if a request is made.
669 | * The test will pass whether or not the request gets made during testing.
670 | *
671 | *
672 | *
673 | *
Request expectations
Backend definitions
674 | *
675 | *
Syntax
676 | *
.expect(...).respond(...)
677 | *
.when(...).respond(...)
678 | *
679 | *
680 | *
Typical usage
681 | *
strict unit tests
682 | *
loose (black-box) unit testing
683 | *
684 | *
685 | *
Fulfills multiple requests
686 | *
NO
687 | *
YES
688 | *
689 | *
690 | *
Order of requests matters
691 | *
YES
692 | *
NO
693 | *
694 | *
695 | *
Request required
696 | *
YES
697 | *
NO
698 | *
699 | *
700 | *
Response required
701 | *
optional (see below)
702 | *
YES
703 | *
704 | *
705 | *
706 | * In cases where both backend definitions and request expectations are specified during unit
707 | * testing, the request expectations are evaluated first.
708 | *
709 | * If a request expectation has no response specified, the algorithm will search your backend
710 | * definitions for an appropriate response.
711 | *
712 | * If a request didn't match any expectation or if the expectation doesn't have the response
713 | * defined, the backend definitions are evaluated in sequential order to see if any of them match
714 | * the request. The response from the first matched definition is returned.
715 | *
716 | *
717 | * # Flushing HTTP requests
718 | *
719 | * The $httpBackend used in production, always responds to requests with responses asynchronously.
720 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are
721 | * hard to write, follow and maintain. At the same time the testing mock, can't respond
722 | * synchronously because that would change the execution of the code under test. For this reason the
723 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending
724 | * requests and thus preserving the async api of the backend, while allowing the test to execute
725 | * synchronously.
726 | *
727 | *
728 | * # Unit testing with mock $httpBackend
729 | *
730 | *
731 | // controller
732 | function MyController($scope, $http) {
733 | $http.get('/auth.py').success(function(data) {
734 | $scope.user = data;
735 | });
736 |
737 | this.saveMessage = function(message) {
738 | $scope.status = 'Saving...';
739 | $http.post('/add-msg.py', message).success(function(response) {
740 | $scope.status = '';
741 | }).error(function() {
742 | $scope.status = 'ERROR!';
743 | });
744 | };
745 | }
746 |
747 | // testing controller
748 | var $http;
749 |
750 | beforeEach(inject(function($injector) {
751 | $httpBackend = $injector.get('$httpBackend');
752 |
753 | // backend definition common for all tests
754 | $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
755 | }));
756 |
757 |
758 | afterEach(function() {
759 | $httpBackend.verifyNoOutstandingExpectation();
760 | $httpBackend.verifyNoOutstandingRequest();
761 | });
762 |
763 |
764 | it('should fetch authentication token', function() {
765 | $httpBackend.expectGET('/auth.py');
766 | var controller = scope.$new(MyController);
767 | $httpBackend.flush();
768 | });
769 |
770 |
771 | it('should send msg to server', function() {
772 | // now you don’t care about the authentication, but
773 | // the controller will still send the request and
774 | // $httpBackend will respond without you having to
775 | // specify the expectation and response for this request
776 | $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
777 |
778 | var controller = scope.$new(MyController);
779 | $httpBackend.flush();
780 | controller.saveMessage('message content');
781 | expect(controller.status).toBe('Saving...');
782 | $httpBackend.flush();
783 | expect(controller.status).toBe('');
784 | });
785 |
786 |
787 | it('should send auth header', function() {
788 | $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
789 | // check if the header was send, if it wasn't the expectation won't
790 | // match the request and the test will fail
791 | return headers['Authorization'] == 'xxx';
792 | }).respond(201, '');
793 |
794 | var controller = scope.$new(MyController);
795 | controller.saveMessage('whatever');
796 | $httpBackend.flush();
797 | });
798 |
799 | */
800 | angular.mock.$HttpBackendProvider = function() {
801 | this.$get = [createHttpBackendMock];
802 | };
803 |
804 | /**
805 | * General factory function for $httpBackend mock.
806 | * Returns instance for unit testing (when no arguments specified):
807 | * - passing through is disabled
808 | * - auto flushing is disabled
809 | *
810 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified):
811 | * - passing through (delegating request to real backend) is enabled
812 | * - auto flushing is enabled
813 | *
814 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified)
815 | * @param {Object=} $browser Auto-flushing enabled if specified
816 | * @return {Object} Instance of $httpBackend mock
817 | */
818 | function createHttpBackendMock($delegate, $browser) {
819 | var definitions = [],
820 | expectations = [],
821 | responses = [],
822 | responsesPush = angular.bind(responses, responses.push);
823 |
824 | function createResponse(status, data, headers) {
825 | if (angular.isFunction(status)) return status;
826 |
827 | return function() {
828 | return angular.isNumber(status)
829 | ? [status, data, headers]
830 | : [200, status, data];
831 | };
832 | }
833 |
834 | // TODO(vojta): change params to: method, url, data, headers, callback
835 | function $httpBackend(method, url, data, callback, headers) {
836 | var xhr = new MockXhr(),
837 | expectation = expectations[0],
838 | wasExpected = false;
839 |
840 | function prettyPrint(data) {
841 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
842 | ? data
843 | : angular.toJson(data);
844 | }
845 |
846 | if (expectation && expectation.match(method, url)) {
847 | if (!expectation.matchData(data))
848 | throw Error('Expected ' + expectation + ' with different data\n' +
849 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
850 |
851 | if (!expectation.matchHeaders(headers))
852 | throw Error('Expected ' + expectation + ' with different headers\n' +
853 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
854 | prettyPrint(headers));
855 |
856 | expectations.shift();
857 |
858 | if (expectation.response) {
859 | responses.push(function() {
860 | var response = expectation.response(method, url, data, headers);
861 | xhr.$$respHeaders = response[2];
862 | callback(response[0], response[1], xhr.getAllResponseHeaders());
863 | });
864 | return;
865 | }
866 | wasExpected = true;
867 | }
868 |
869 | var i = -1, definition;
870 | while ((definition = definitions[++i])) {
871 | if (definition.match(method, url, data, headers || {})) {
872 | if (definition.response) {
873 | // if $browser specified, we do auto flush all requests
874 | ($browser ? $browser.defer : responsesPush)(function() {
875 | var response = definition.response(method, url, data, headers);
876 | xhr.$$respHeaders = response[2];
877 | callback(response[0], response[1], xhr.getAllResponseHeaders());
878 | });
879 | } else if (definition.passThrough) {
880 | $delegate(method, url, data, callback, headers);
881 | } else throw Error('No response defined !');
882 | return;
883 | }
884 | }
885 | throw wasExpected ?
886 | Error('No response defined !') :
887 | Error('Unexpected request: ' + method + ' ' + url + '\n' +
888 | (expectation ? 'Expected ' + expectation : 'No more request expected'));
889 | }
890 |
891 | /**
892 | * @ngdoc method
893 | * @name ngMock.$httpBackend#when
894 | * @methodOf ngMock.$httpBackend
895 | * @description
896 | * Creates a new backend definition.
897 | *
898 | * @param {string} method HTTP method.
899 | * @param {string|RegExp} url HTTP url.
900 | * @param {(string|RegExp)=} data HTTP request body.
901 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
902 | * object and returns true if the headers match the current definition.
903 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
904 | * request is handled.
905 | *
906 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
907 | * – The respond method takes a set of static data to be returned or a function that can return
908 | * an array containing response status (number), response data (string) and response headers
909 | * (Object).
910 | */
911 | $httpBackend.when = function(method, url, data, headers) {
912 | var definition = new MockHttpExpectation(method, url, data, headers),
913 | chain = {
914 | respond: function(status, data, headers) {
915 | definition.response = createResponse(status, data, headers);
916 | }
917 | };
918 |
919 | if ($browser) {
920 | chain.passThrough = function() {
921 | definition.passThrough = true;
922 | };
923 | }
924 |
925 | definitions.push(definition);
926 | return chain;
927 | };
928 |
929 | /**
930 | * @ngdoc method
931 | * @name ngMock.$httpBackend#whenGET
932 | * @methodOf ngMock.$httpBackend
933 | * @description
934 | * Creates a new backend definition for GET requests. For more info see `when()`.
935 | *
936 | * @param {string|RegExp} url HTTP url.
937 | * @param {(Object|function(Object))=} headers HTTP headers.
938 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
939 | * request is handled.
940 | */
941 |
942 | /**
943 | * @ngdoc method
944 | * @name ngMock.$httpBackend#whenHEAD
945 | * @methodOf ngMock.$httpBackend
946 | * @description
947 | * Creates a new backend definition for HEAD requests. For more info see `when()`.
948 | *
949 | * @param {string|RegExp} url HTTP url.
950 | * @param {(Object|function(Object))=} headers HTTP headers.
951 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
952 | * request is handled.
953 | */
954 |
955 | /**
956 | * @ngdoc method
957 | * @name ngMock.$httpBackend#whenDELETE
958 | * @methodOf ngMock.$httpBackend
959 | * @description
960 | * Creates a new backend definition for DELETE requests. For more info see `when()`.
961 | *
962 | * @param {string|RegExp} url HTTP url.
963 | * @param {(Object|function(Object))=} headers HTTP headers.
964 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
965 | * request is handled.
966 | */
967 |
968 | /**
969 | * @ngdoc method
970 | * @name ngMock.$httpBackend#whenPOST
971 | * @methodOf ngMock.$httpBackend
972 | * @description
973 | * Creates a new backend definition for POST requests. For more info see `when()`.
974 | *
975 | * @param {string|RegExp} url HTTP url.
976 | * @param {(string|RegExp)=} data HTTP request body.
977 | * @param {(Object|function(Object))=} headers HTTP headers.
978 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
979 | * request is handled.
980 | */
981 |
982 | /**
983 | * @ngdoc method
984 | * @name ngMock.$httpBackend#whenPUT
985 | * @methodOf ngMock.$httpBackend
986 | * @description
987 | * Creates a new backend definition for PUT requests. For more info see `when()`.
988 | *
989 | * @param {string|RegExp} url HTTP url.
990 | * @param {(string|RegExp)=} data HTTP request body.
991 | * @param {(Object|function(Object))=} headers HTTP headers.
992 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
993 | * request is handled.
994 | */
995 |
996 | /**
997 | * @ngdoc method
998 | * @name ngMock.$httpBackend#whenJSONP
999 | * @methodOf ngMock.$httpBackend
1000 | * @description
1001 | * Creates a new backend definition for JSONP requests. For more info see `when()`.
1002 | *
1003 | * @param {string|RegExp} url HTTP url.
1004 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1005 | * request is handled.
1006 | */
1007 | createShortMethods('when');
1008 |
1009 |
1010 | /**
1011 | * @ngdoc method
1012 | * @name ngMock.$httpBackend#expect
1013 | * @methodOf ngMock.$httpBackend
1014 | * @description
1015 | * Creates a new request expectation.
1016 | *
1017 | * @param {string} method HTTP method.
1018 | * @param {string|RegExp} url HTTP url.
1019 | * @param {(string|RegExp)=} data HTTP request body.
1020 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1021 | * object and returns true if the headers match the current expectation.
1022 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1023 | * request is handled.
1024 | *
1025 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
1026 | * – The respond method takes a set of static data to be returned or a function that can return
1027 | * an array containing response status (number), response data (string) and response headers
1028 | * (Object).
1029 | */
1030 | $httpBackend.expect = function(method, url, data, headers) {
1031 | var expectation = new MockHttpExpectation(method, url, data, headers);
1032 | expectations.push(expectation);
1033 | return {
1034 | respond: function(status, data, headers) {
1035 | expectation.response = createResponse(status, data, headers);
1036 | }
1037 | };
1038 | };
1039 |
1040 |
1041 | /**
1042 | * @ngdoc method
1043 | * @name ngMock.$httpBackend#expectGET
1044 | * @methodOf ngMock.$httpBackend
1045 | * @description
1046 | * Creates a new request expectation for GET requests. For more info see `expect()`.
1047 | *
1048 | * @param {string|RegExp} url HTTP url.
1049 | * @param {Object=} headers HTTP headers.
1050 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1051 | * request is handled. See #expect for more info.
1052 | */
1053 |
1054 | /**
1055 | * @ngdoc method
1056 | * @name ngMock.$httpBackend#expectHEAD
1057 | * @methodOf ngMock.$httpBackend
1058 | * @description
1059 | * Creates a new request expectation for HEAD requests. For more info see `expect()`.
1060 | *
1061 | * @param {string|RegExp} url HTTP url.
1062 | * @param {Object=} headers HTTP headers.
1063 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1064 | * request is handled.
1065 | */
1066 |
1067 | /**
1068 | * @ngdoc method
1069 | * @name ngMock.$httpBackend#expectDELETE
1070 | * @methodOf ngMock.$httpBackend
1071 | * @description
1072 | * Creates a new request expectation for DELETE requests. For more info see `expect()`.
1073 | *
1074 | * @param {string|RegExp} url HTTP url.
1075 | * @param {Object=} headers HTTP headers.
1076 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1077 | * request is handled.
1078 | */
1079 |
1080 | /**
1081 | * @ngdoc method
1082 | * @name ngMock.$httpBackend#expectPOST
1083 | * @methodOf ngMock.$httpBackend
1084 | * @description
1085 | * Creates a new request expectation for POST requests. For more info see `expect()`.
1086 | *
1087 | * @param {string|RegExp} url HTTP url.
1088 | * @param {(string|RegExp)=} data HTTP request body.
1089 | * @param {Object=} headers HTTP headers.
1090 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1091 | * request is handled.
1092 | */
1093 |
1094 | /**
1095 | * @ngdoc method
1096 | * @name ngMock.$httpBackend#expectPUT
1097 | * @methodOf ngMock.$httpBackend
1098 | * @description
1099 | * Creates a new request expectation for PUT requests. For more info see `expect()`.
1100 | *
1101 | * @param {string|RegExp} url HTTP url.
1102 | * @param {(string|RegExp)=} data HTTP request body.
1103 | * @param {Object=} headers HTTP headers.
1104 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1105 | * request is handled.
1106 | */
1107 |
1108 | /**
1109 | * @ngdoc method
1110 | * @name ngMock.$httpBackend#expectPATCH
1111 | * @methodOf ngMock.$httpBackend
1112 | * @description
1113 | * Creates a new request expectation for PATCH requests. For more info see `expect()`.
1114 | *
1115 | * @param {string|RegExp} url HTTP url.
1116 | * @param {(string|RegExp)=} data HTTP request body.
1117 | * @param {Object=} headers HTTP headers.
1118 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1119 | * request is handled.
1120 | */
1121 |
1122 | /**
1123 | * @ngdoc method
1124 | * @name ngMock.$httpBackend#expectJSONP
1125 | * @methodOf ngMock.$httpBackend
1126 | * @description
1127 | * Creates a new request expectation for JSONP requests. For more info see `expect()`.
1128 | *
1129 | * @param {string|RegExp} url HTTP url.
1130 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched
1131 | * request is handled.
1132 | */
1133 | createShortMethods('expect');
1134 |
1135 |
1136 | /**
1137 | * @ngdoc method
1138 | * @name ngMock.$httpBackend#flush
1139 | * @methodOf ngMock.$httpBackend
1140 | * @description
1141 | * Flushes all pending requests using the trained responses.
1142 | *
1143 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
1144 | * all pending requests will be flushed. If there are no pending requests when the flush method
1145 | * is called an exception is thrown (as this typically a sign of programming error).
1146 | */
1147 | $httpBackend.flush = function(count) {
1148 | if (!responses.length) throw Error('No pending request to flush !');
1149 |
1150 | if (angular.isDefined(count)) {
1151 | while (count--) {
1152 | if (!responses.length) throw Error('No more pending request to flush !');
1153 | responses.shift()();
1154 | }
1155 | } else {
1156 | while (responses.length) {
1157 | responses.shift()();
1158 | }
1159 | }
1160 | $httpBackend.verifyNoOutstandingExpectation();
1161 | };
1162 |
1163 |
1164 | /**
1165 | * @ngdoc method
1166 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation
1167 | * @methodOf ngMock.$httpBackend
1168 | * @description
1169 | * Verifies that all of the requests defined via the `expect` api were made. If any of the
1170 | * requests were not made, verifyNoOutstandingExpectation throws an exception.
1171 | *
1172 | * Typically, you would call this method following each test case that asserts requests using an
1173 | * "afterEach" clause.
1174 | *
1175 | *
41 |
--------------------------------------------------------------------------------
/src/img/docs/demo.js:
--------------------------------------------------------------------------------
1 | var ImageDemoCtrl = function ( $scope ) {
2 | $scope.imageDimension = '550x300';
3 | };
4 |
--------------------------------------------------------------------------------
/src/img/docs/readme.md:
--------------------------------------------------------------------------------
1 | The `phImg` directive creates client-side placeholder images in any
2 | size. The directive creates a PNG image using the HTML5 canvas library and
3 | uses the generated client-side URL as either (a) the `src` attribute, if the
4 | element is an `img`, or (b) the `css-background` for all other types of
5 | elements.
6 |
7 | The directive takes a single eponymous attribute that specifies the dimensions
8 | of the image to create; the expected format is "100x100".
9 |
--------------------------------------------------------------------------------
/src/img/img.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Permission is hereby granted, free of charge, to any person obtaining a copy
3 | * of this software and associated documentation files (the "Software"), to
4 | * deal in the Software without restriction, including without limitation the
5 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
6 | * sell copies of the Software, and to permit persons to whom the Software is
7 | * furnished to do so, subject to the following conditions:
8 | *
9 | * The above copyright notice and this permission notice shall be included in
10 | * all copies or substantial portions of the Software.
11 | *
12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
18 | * IN THE SOFTWARE.
19 | */
20 |
21 | /**
22 | * Creates a placeholder image client-side for use during design and
23 | * development.
24 | *
25 | * TODO: Right now, it only supports `img` tags. This should be enforced in code
26 | * and default to adding a CSS `background-image` for non-`img` elements.
27 | */
28 | angular.module( 'placeholders.img', [] )
29 | .directive( 'phImg', function () {
30 | return {
31 | restrict: 'A',
32 | scope: { dimensions: '@phImg' },
33 | link: function( scope, element, attr ) {
34 | // A reference to a canvas that we can reuse
35 | var canvas;
36 |
37 | /**
38 | * The configurable parameters of the placeholder image.
39 | *
40 | * TODO: make configurable
41 | * TODO: make defaultable
42 | */
43 | var config = {
44 | text_size: 10,
45 | fill_color: '#EEEEEE',
46 | text_color: '#AAAAAA'
47 | };
48 |
49 | /**
50 | * When the provided dimensions change, re-pull the width and height and
51 | * then redraw the image.
52 | */
53 | scope.$watch('dimensions', function () {
54 | if( ! angular.isDefined( scope.dimensions ) ) {
55 | return;
56 | }
57 | var matches = scope.dimensions.match( /^(\d+)x(\d+)$/ ),
58 | dataUrl;
59 |
60 | if( ! matches ) {
61 | console.error("Expected '000x000'. Got " + scope.dimensions);
62 | return;
63 | }
64 |
65 | // Grab the provided dimensions.
66 | scope.size = { w: matches[1], h: matches[2] };
67 |
68 | // FIXME: only add these if not already present
69 | element.prop( "title", scope.dimensions );
70 | element.prop( "alt", scope.dimensions );
71 |
72 | // And draw the image, getting the returned data URL.
73 | dataUrl = drawImage();
74 |
75 | // If this is an `img` tag, set the src as the data URL. Else, we set
76 | // the CSS `background-image` property to same.
77 | if ( element.prop( "tagName" ) === "IMG" ) {
78 | element.prop( 'src', dataUrl );
79 | } else {
80 | element.css( 'background-image', 'url("' + dataUrl + '")' );
81 | }
82 | });
83 |
84 | /**
85 | * Calculate the maximum height of the text we can draw, based on the
86 | * requested dimensions of the image.
87 | */
88 | function getTextSize() {
89 | var dimension_arr = [scope.size.h, scope.size.w].sort(),
90 | maxFactor = Math.round(dimension_arr[1] / 16);
91 |
92 | return Math.max(config.text_size, maxFactor);
93 | }
94 |
95 | /**
96 | * Using the HTML5 canvas API, draw a placeholder image of the requested
97 | * size with text centered vertically and horizontally that specifies its
98 | * dimensions. Returns the data URL that can be used as an `img`'s `src`
99 | * attribute.
100 | */
101 | function drawImage() {
102 | // Create a new canvas if we don't already have one. We reuse the canvas
103 | // when if gets redrawn so as not to be wasteful.
104 | canvas = canvas || document.createElement( 'canvas' );
105 |
106 | // Obtain a 2d drawing context on which we can add the placeholder
107 | // image.
108 | var context = canvas.getContext( '2d' ),
109 | text_size,
110 | text;
111 |
112 | // Set the canvas to the appropriate size.
113 | canvas.width = scope.size.w;
114 | canvas.height = scope.size.h;
115 |
116 | // Draw the placeholder image square.
117 | // TODO: support other shapes
118 | // TODO: support configurable colors
119 | context.fillStyle = config.fill_color;
120 | context.fillRect( 0, 0, scope.size.w, scope.size.h );
121 |
122 | // Add the dimension text.
123 | // TODO: support configurable font
124 | // FIXME: ensure text will fit and resize if it doesn't
125 | text_size = getTextSize();
126 | text = scope.dimensions;
127 | context.fillStyle = config.text_color;
128 | context.textAlign = 'center';
129 | context.textBaseline = 'middle';
130 | context.font = 'bold '+text_size+'pt sans-serif';
131 |
132 | // If the text is too long to fit, reduce it until it will.
133 | if (context.measureText( text ).width / scope.size.w > 1) {
134 | text_size = config.text_size / (context.measureText( text ).width / scope.size.w);
135 | context.font = 'bold '+text_size+'pt sans-serif';
136 | }
137 |
138 | // Finally, draw the text in its calculated position.
139 | context.fillText( scope.dimensions, scope.size.w / 2, scope.size.h / 2 );
140 |
141 | // Get the data URL and return it.
142 | return canvas.toDataURL("image/png");
143 | }
144 | }
145 | };
146 | });
147 |
148 |
--------------------------------------------------------------------------------
/src/img/test/imgSpec.js:
--------------------------------------------------------------------------------
1 | describe( 'phImg', function () {
2 | beforeEach( module( 'placeholders.img' ) );
3 |
4 | beforeEach( inject( function ( _$rootScope_, _$compile_) {
5 | $compile = _$compile_;
6 | scope = _$rootScope_;
7 |
8 | scope.w = 300;
9 | scope.h = 150;
10 |
11 | element = $compile( '' )( scope );
12 | scope.$digest();
13 | }));
14 |
15 | it( 'should create an image of appropriate dimensions', inject( function () {
16 | // Unfortunately, it can take just a few milliseconds sometimes for the
17 | // creation of the canvas and assigning its data url to the image. So we
18 | // have to call this asynchronously.
19 | waits(10);
20 | runs(function () {
21 | expect( element.prop("src") ).toBeTruthy();
22 |
23 | expect( element.prop( 'naturalWidth' ) ).toBe( scope.w );
24 | expect( element.prop( 'naturalHeight' ) ).toBe( scope.h );
25 | });
26 | }));
27 |
28 | it( 'should add the title and alt attributes', inject( function () {
29 | var dimensions = scope.w+'x'+scope.h;
30 | expect( element.prop( 'alt' ) ).toBe( dimensions );
31 | expect( element.prop( 'title' ) ).toBe( dimensions );
32 | }));
33 |
34 | it( 'should set the CSS `background-image` property if tag is not an img', inject( function () {
35 | element = $compile( '' )( scope );
36 | scope.$digest();
37 |
38 | var bgImg = element.css( 'background-image' );
39 | expect( bgImg.length ).toBeGreaterThan( 0 );
40 | }));
41 | });
42 |
43 |
--------------------------------------------------------------------------------
/src/txt/docs/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
Here's a few sentences
3 |
# Sentences:
4 |
5 |
6 |
And a few paragraphs
7 |
# Paragraphs:
8 |
9 |
10 |
And combining both:
11 |
# Paragraphs:
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/txt/docs/demo.js:
--------------------------------------------------------------------------------
1 | var TextDemoCtrl = function ( $scope ) {
2 | $scope.numSentences = "3";
3 | $scope.numParagraphs = "2";
4 | $scope.numCombined = "2p3s";
5 | };
6 |
--------------------------------------------------------------------------------
/src/txt/docs/readme.md:
--------------------------------------------------------------------------------
1 | The `phTxt` directive dynamically inserts "Lorem ipsum"-style text
2 | into an element. It can work as either an element or an attribute.
3 |
4 | `phTxt` accepts a single value that can express the number of desired paragraphs
5 | and/or sentences. The format is `#p#s`, where the numbers represent the number
6 | of requested paragraphs and sentences, respectively. Order is irrelevant.
7 |
8 | If both are provided, the element will contain the requested number of
9 | paragraphs, each with the requested number of sentences.
10 |
11 | If just the number of paragraphs is provided, the number of sentences will be
12 | random for each paragraph. If just the number of sentences is provided, no
13 | paragraphs will be generated - just the specified number of sentences.
14 |
15 | If neither are provided, the default behavior is a random number of paragraphs,
16 | each with a random number of sentences.
17 |
--------------------------------------------------------------------------------
/src/txt/test/txtSpec.js:
--------------------------------------------------------------------------------
1 | describe( 'TextGeneratorService', function () {
2 | // Load the module.
3 | beforeEach(module('placeholders.txt'));
4 |
5 | beforeEach(inject(function( _TextGeneratorService_ ) {
6 | TextGeneratorService = _TextGeneratorService_;
7 | }));
8 |
9 | it( 'should create a sentence with a specified word count', inject(function () {
10 | var sentenceLength = 10,
11 | sentence = TextGeneratorService.createSentence( sentenceLength ).split( ' ' );
12 |
13 | expect( sentence.length ).toBe( sentenceLength );
14 | }));
15 |
16 | it( 'should create a sentence with a random word count', inject(function () {
17 | var sentence = TextGeneratorService.createSentence().split( ' ' );
18 |
19 | expect( sentence.length ).toBeGreaterThan( 4 );
20 | expect( sentence.length ).toBeLessThan( 21 );
21 | }));
22 |
23 | it( 'should create a random number of sentences', inject(function () {
24 | var sentences = TextGeneratorService.createSentences().split( '.' );
25 |
26 | expect( sentences.length - 1 ).toBeGreaterThan( 2 );
27 | expect( sentences.length - 1 ).toBeLessThan( 6 );
28 | }));
29 |
30 | it( 'should create a specified number of sentences', inject(function () {
31 | var sentenceCount = 5,
32 | sentences = TextGeneratorService.createSentences( sentenceCount ).split( '.' );
33 |
34 | expect( sentences.length - 1 ).toBe( sentenceCount );
35 | }));
36 |
37 | it( 'should create a paragraph with a random number of sentences', inject(function () {
38 | var sentences = TextGeneratorService.createParagraph().split( '.' );
39 |
40 | expect( sentences.length - 1 ).toBeGreaterThan( 2 );
41 | expect( sentences.length - 1 ).toBeLessThan( 6 );
42 | }));
43 |
44 | it( 'should create a paragraph with a specified number of sentences', inject(function () {
45 | var sentenceCount = 5,
46 | sentences = TextGeneratorService.createParagraph( sentenceCount ).split( '.' );
47 |
48 | expect( sentences.length - 1 ).toBe( sentenceCount );
49 | }));
50 |
51 | it( 'should create a random number of paragraphs', inject(function () {
52 | var paragraphs = TextGeneratorService.createParagraphs().split( '\n' );
53 |
54 | expect( paragraphs.length ).toBeGreaterThan( 2 );
55 | expect( paragraphs.length ).toBeLessThan( 8 );
56 | }));
57 |
58 | it( 'should create a specified number of paragraphs', inject(function () {
59 | var paragraphCount = 5,
60 | paragraphs = TextGeneratorService.createParagraphs( paragraphCount ).split( '\n' );
61 |
62 | expect( paragraphs.length ).toBe( paragraphCount );
63 | }));
64 | });
65 |
66 | describe( 'phTxt Directive', function () {
67 | // Load the module.
68 | beforeEach(module('placeholders.txt'));
69 |
70 | beforeEach(inject(function (_$rootScope_, _$compile_) {
71 | scope = _$rootScope_;
72 | $compile = _$compile_;
73 | }));
74 |
75 | it( 'should add random paragraphs by default', function () {
76 | var tpl = '',
77 | element = $compile( tpl )( scope ),
78 | paragraphCount;
79 |
80 | paragraphCount = element.find('p').length;
81 |
82 | expect( paragraphCount ).toBeGreaterThan( 2 );
83 | expect( paragraphCount ).toBeLessThan( 8 );
84 | });
85 |
86 | it( 'should add a specified number of paragraphs', function () {
87 |
88 | var tpl = '',
89 | element = $compile( tpl )( scope ),
90 | paragraphCount;
91 |
92 | scope.numParagraphs = 5;
93 | scope.$digest();
94 |
95 | paragraphCount = element.find('p').length;
96 |
97 | expect( paragraphCount ).toBe( scope.numParagraphs );
98 | });
99 |
100 | it( 'should add the specified number of sentences', function () {
101 |
102 | var tpl = '',
103 | element = $compile( tpl )( scope ),
104 | text;
105 |
106 | scope.numSentences = 5;
107 | scope.$digest();
108 |
109 | // Get the inner text of the element.
110 | text = element.text();
111 |
112 | // It should have the specified number of sentences. We have to take one
113 | // *less* than the length after the split because 5 periods makes 6
114 | // segments.
115 | expect( text.split('.').length - 1 ).toBe( scope.numSentences );
116 | });
117 |
118 | it( 'should add the specified number of sentences in the specified # of paragraphs', function () {
119 |
120 | var tpl = '',
121 | element = $compile( tpl )( scope ),
122 | paragraphs;
123 |
124 | scope.numSentences = 5;
125 | scope.numParagraphs = 3;
126 | scope.$digest();
127 |
128 |
129 | paragraphs = element.find('p');
130 | expect( paragraphs.length ).toBe( scope.numParagraphs );
131 |
132 | angular.forEach( paragraphs, function ( p ) {
133 | // Get the inner text of the element.
134 | var text = angular.element( p ).text();
135 |
136 | // It should have the specified number of sentences. We have to take one
137 | // *less* than the length after the split because 5 periods makes 6
138 | // segments.
139 | expect( text.split('.').length - 1 ).toBe( scope.numSentences );
140 | });
141 | });
142 |
143 | });
144 |
145 |
--------------------------------------------------------------------------------
/src/txt/txt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Permission is hereby granted, free of charge, to any person obtaining a copy
3 | * of this software and associated documentation files (the "Software"), to
4 | * deal in the Software without restriction, including without limitation the
5 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
6 | * sell copies of the Software, and to permit persons to whom the Software is
7 | * furnished to do so, subject to the following conditions:
8 | *
9 | * The above copyright notice and this permission notice shall be included in
10 | * all copies or substantial portions of the Software.
11 | *
12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
18 | * IN THE SOFTWARE.
19 | */
20 |
21 | /**
22 | * This is based, in part, on [fkadeveloper](https://github.com/fkadeveloper)'s
23 | * [lorem.js](https://github.com/fkadeveloper/loremjs).
24 | */
25 | angular.module( 'placeholders.txt', [] )
26 | .factory( 'TextGeneratorService', function () {
27 | var words = ["lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing",
28 | "elit", "ut", "aliquam,", "purus", "sit", "amet", "luctus", "venenatis,",
29 | "lectus", "magna", "fringilla", "urna,", "porttitor", "rhoncus", "dolor",
30 | "purus", "non", "enim", "praesent", "elementum", "facilisis", "leo,", "vel",
31 | "fringilla", "est", "ullamcorper", "eget", "nulla", "facilisi", "etiam",
32 | "dignissim", "diam", "quis", "enim", "lobortis", "scelerisque", "fermentum",
33 | "dui", "faucibus", "in", "ornare", "quam", "viverra", "orci", "sagittis", "eu",
34 | "volutpat", "odio", "facilisis", "mauris", "sit", "amet", "massa", "vitae",
35 | "tortor", "condimentum", "lacinia", "quis", "vel", "eros", "donec", "ac",
36 | "odio", "tempor", "orci", "dapibus", "ultrices", "in", "iaculis", "nunc",
37 | "sed", "augue", "lacus,", "viverra", "vitae", "congue", "eu,", "consequat",
38 | "ac", "felis", "donec", "et", "odio", "pellentesque", "diam", "volutpat",
39 | "commodo", "sed", "egestas", "egestas", "fringilla", "phasellus", "faucibus",
40 | "scelerisque", "eleifend", "donec", "pretium", "vulputate", "sapien", "nec",
41 | "sagittis", "aliquam", "malesuada", "bibendum", "arcu", "vitae", "elementum",
42 | "curabitur", "vitae", "nunc", "sed", "velit", "dignissim", "sodales", "ut",
43 | "eu", "sem", "integer", "vitae", "justo", "eget", "magna", "fermentum",
44 | "iaculis", "eu", "non", "diam", "phasellus", "vestibulum", "lorem", "sed",
45 | "risus", "ultricies", "tristique", "nulla", "aliquet", "enim", "tortor,", "at",
46 | "auctor", "urna", "nunc", "id", "cursus", "metus", "aliquam", "eleifend", "mi",
47 | "in", "nulla", "posuere", "sollicitudin", "aliquam", "ultrices", "sagittis",
48 | "orci,", "a", "scelerisque", "purus", "semper", "eget", "duis", "at", "tellus",
49 | "at", "urna", "condimentum", "mattis", "pellentesque", "id", "nibh", "tortor,",
50 | "id", "aliquet", "lectus", "proin", "nibh", "nisl,", "condimentum", "id",
51 | "venenatis", "a,", "condimentum", "vitae", "sapien", "pellentesque",
52 | "habitant", "morbi", "tristique", "senectus", "et", "netus", "et", "malesuada",
53 | "fames", "ac", "turpis", "egestas", "sed", "tempus,", "urna", "et", "pharetra",
54 | "pharetra,", "massa", "massa", "ultricies", "mi,", "quis", "hendrerit",
55 | "dolor", "magna", "eget", "est", "lorem", "ipsum", "dolor", "sit", "amet,",
56 | "consectetur", "adipiscing", "elit", "pellentesque", "habitant", "morbi",
57 | "tristique", "senectus", "et", "netus", "et", "malesuada", "fames", "ac",
58 | "turpis", "egestas", "integer", "eget", "aliquet", "nibh", "praesent",
59 | "tristique", "magna", "sit", "amet", "purus", "gravida", "quis", "blandit",
60 | "turpis", "cursus", "in", "hac", "habitasse", "platea", "dictumst", "quisque",
61 | "sagittis,", "purus", "sit", "amet", "volutpat", "consequat,", "mauris",
62 | "nunc", "congue", "nisi,", "vitae", "suscipit", "tellus", "mauris", "a",
63 | "diam", "maecenas", "sed", "enim", "ut", "sem", "viverra", "aliquet", "eget",
64 | "sit", "amet", "tellus", "cras", "adipiscing", "enim", "eu", "turpis",
65 | "egestas", "pretium", "aenean", "pharetra,", "magna", "ac", "placerat",
66 | "vestibulum,", "lectus", "mauris", "ultrices", "eros,", "in", "cursus",
67 | "turpis", "massa", "tincidunt", "dui", "ut", "ornare", "lectus", "sit", "amet",
68 | "est", "placerat", "in", "egestas", "erat", "imperdiet", "sed", "euismod",
69 | "nisi", "porta", "lorem", "mollis", "aliquam", "ut", "porttitor", "leo", "a",
70 | "diam", "sollicitudin", "tempor", "id", "eu", "nisl", "nunc", "mi", "ipsum,",
71 | "faucibus", "vitae", "aliquet", "nec,", "ullamcorper", "sit", "amet", "risus",
72 | "nullam", "eget", "felis", "eget", "nunc", "lobortis", "mattis", "aliquam",
73 | "faucibus", "purus", "in", "massa", "tempor", "nec", "feugiat", "nisl",
74 | "pretium", "fusce", "id", "velit", "ut", "tortor", "pretium", "viverra",
75 | "suspendisse", "potenti", "nullam", "ac", "tortor", "vitae", "purus",
76 | "faucibus", "ornare", "suspendisse", "sed", "nisi", "lacus,", "sed", "viverra",
77 | "tellus", "in", "hac", "habitasse", "platea", "dictumst", "vestibulum",
78 | "rhoncus", "est", "pellentesque", "elit", "ullamcorper", "dignissim", "cras",
79 | "tincidunt", "lobortis", "feugiat", "vivamus", "at", "augue", "eget", "arcu",
80 | "dictum", "varius", "duis", "at", "consectetur", "lorem", "donec", "massa",
81 | "sapien,", "faucibus", "et", "molestie", "ac,", "feugiat", "sed", "lectus",
82 | "vestibulum", "mattis", "ullamcorper", "velit", "sed", "ullamcorper", "morbi",
83 | "tincidunt", "ornare", "massa,", "eget", "egestas", "purus", "viverra",
84 | "accumsan", "in", "nisl", "nisi,", "scelerisque", "eu", "ultrices", "vitae,",
85 | "auctor", "eu", "augue", "ut", "lectus", "arcu,", "bibendum", "at", "varius",
86 | "vel,", "pharetra", "vel", "turpis", "nunc", "eget", "lorem", "dolor,", "sed",
87 | "viverra", "ipsum", "nunc", "aliquet", "bibendum", "enim,", "facilisis",
88 | "gravida", "neque", "convallis", "a", "cras", "semper", "auctor", "neque,",
89 | "vitae", "tempus", "quam", "pellentesque", "nec", "nam", "aliquam", "sem",
90 | "et", "tortor", "consequat", "id", "porta", "nibh", "venenatis", "cras", "sed",
91 | "felis", "eget", "velit", "aliquet", "sagittis", "id", "consectetur", "purus",
92 | "ut", "faucibus", "pulvinar", "elementum", "integer", "enim", "neque,",
93 | "volutpat", "ac", "tincidunt", "vitae,", "semper", "quis", "lectus", "nulla",
94 | "at", "volutpat", "diam", "ut", "venenatis", "tellus", "in", "metus",
95 | "vulputate", "eu", "scelerisque", "felis", "imperdiet", "proin", "fermentum",
96 | "leo", "vel", "orci", "porta", "non", "pulvinar", "neque", "laoreet",
97 | "suspendisse", "interdum", "consectetur", "libero,", "id", "faucibus", "nisl",
98 | "tincidunt", "eget", "nullam", "non", "nisi", "est,", "sit", "amet",
99 | "facilisis", "magna", "etiam", "tempor,", "orci", "eu", "lobortis",
100 | "elementum,", "nibh", "tellus", "molestie", "nunc,", "non", "blandit", "massa",
101 | "enim", "nec", "dui", "nunc", "mattis", "enim", "ut", "tellus", "elementum",
102 | "sagittis", "vitae", "et", "leo", "duis", "ut", "diam", "quam", "nulla",
103 | "porttitor", "massa", "id", "neque", "aliquam", "vestibulum", "morbi",
104 | "blandit", "cursus", "risus,", "at", "ultrices", "mi", "tempus", "imperdiet",
105 | "nulla", "malesuada", "pellentesque", "elit", "eget", "gravida", "cum",
106 | "sociis", "natoque", "penatibus", "et", "magnis", "dis", "parturient",
107 | "montes,", "nascetur", "ridiculus", "mus", "mauris", "vitae", "ultricies",
108 | "leo", "integer", "malesuada", "nunc", "vel", "risus", "commodo", "viverra",
109 | "maecenas", "accumsan,", "lacus", "vel", "facilisis", "volutpat,", "est",
110 | "velit", "egestas", "dui,", "id", "ornare", "arcu", "odio", "ut", "sem",
111 | "nulla", "pharetra", "diam", "sit", "amet", "nisl", "suscipit", "adipiscing",
112 | "bibendum", "est", "ultricies", "integer", "quis", "auctor", "elit", "sed",
113 | "vulputate", "mi", "sit", "amet", "mauris", "commodo", "quis", "imperdiet",
114 | "massa", "tincidunt", "nunc", "pulvinar", "sapien", "et", "ligula",
115 | "ullamcorper", "malesuada", "proin", "libero", "nunc,", "consequat",
116 | "interdum", "varius", "sit", "amet,", "mattis", "vulputate", "enim", "nulla",
117 | "aliquet", "porttitor", "lacus,", "luctus", "accumsan", "tortor", "posuere",
118 | "ac", "ut", "consequat", "semper", "viverra", "nam", "libero", "justo,",
119 | "laoreet", "sit", "amet", "cursus", "sit", "amet,", "dictum", "sit", "amet",
120 | "justo", "donec", "enim", "diam,", "vulputate", "ut", "pharetra", "sit",
121 | "amet,", "aliquam", "id", "diam", "maecenas", "ultricies", "mi", "eget",
122 | "mauris", "pharetra", "et", "ultrices", "neque", "ornare", "aenean", "euismod",
123 | "elementum", "nisi,", "quis", "eleifend", "quam", "adipiscing", "vitae",
124 | "proin", "sagittis,", "nisl", "rhoncus", "mattis", "rhoncus,", "urna", "neque",
125 | "viverra", "justo,", "nec", "ultrices", "dui", "sapien", "eget", "mi", "proin",
126 | "sed", "libero", "enim,", "sed", "faucibus", "turpis", "in", "eu", "mi",
127 | "bibendum", "neque", "egestas", "congue", "quisque", "egestas", "diam", "in",
128 | "arcu", "cursus", "euismod", "quis", "viverra", "nibh", "cras", "pulvinar",
129 | "mattis", "nunc,", "sed", "blandit", "libero", "volutpat", "sed", "cras",
130 | "ornare", "arcu", "dui", "vivamus", "arcu", "felis,", "bibendum", "ut",
131 | "tristique", "et,", "egestas", "quis", "ipsum", "suspendisse", "ultrices",
132 | "fusce", "ut", "placerat", "orci", "nulla", "pellentesque",
133 | "dignissim", "enim,", "sit", "amet", "venenatis", "urna", "cursus", "eget",
134 | "nunc", "scelerisque", "viverra", "mauris,", "in", "aliquam", "sem",
135 | "fringilla", "ut", "morbi", "tincidunt", "augue", "interdum", "velit",
136 | "euismod", "in", "pellentesque", "massa", "placerat", "duis", "ultricies",
137 | "lacus", "sed", "turpis", "tincidunt", "id", "aliquet", "risus", "feugiat",
138 | "in", "ante", "metus,", "dictum", "at", "tempor", "commodo,", "ullamcorper",
139 | "a", "lacus", "vestibulum", "sed", "arcu", "non", "odio", "euismod", "lacinia",
140 | "at", "quis", "risus", "sed", "vulputate", "odio", "ut", "enim", "blandit",
141 | "volutpat", "maecenas", "volutpat", "blandit", "aliquam", "etiam", "erat",
142 | "velit,", "scelerisque", "in", "dictum", "non,", "consectetur", "a", "erat",
143 | "nam", "at", "lectus", "urna", "duis", "convallis", "convallis", "tellus,",
144 | "id", "interdum", "velit", "laoreet", "id", "donec", "ultrices", "tincidunt",
145 | "arcu,", "non", "sodales", "neque", "sodales", "ut", "etiam", "sit", "amet",
146 | "nisl", "purus,", "in", "mollis", "nunc", "sed", "id", "semper", "risus", "in",
147 | "hendrerit", "gravida", "rutrum", "quisque", "non", "tellus", "orci,", "ac",
148 | "auctor", "augue", "mauris", "augue", "neque,", "gravida", "in", "fermentum",
149 | "et,", "sollicitudin", "ac", "orci", "phasellus", "egestas", "tellus",
150 | "rutrum", "tellus", "pellentesque", "eu", "tincidunt", "tortor", "aliquam",
151 | "nulla", "facilisi", "cras", "fermentum,", "odio", "eu", "feugiat", "pretium,",
152 | "nibh", "ipsum", "consequat", "nisl,", "vel", "pretium", "lectus", "quam",
153 | "id", "leo", "in", "vitae", "turpis", "massa", "sed", "elementum", "tempus",
154 | "egestas", "sed", "sed", "risus", "pretium", "quam", "vulputate", "dignissim",
155 | "suspendisse", "in", "est", "ante", "in", "nibh", "mauris,", "cursus",
156 | "mattis", "molestie", "a,", "iaculis", "at", "erat", "pellentesque",
157 | "adipiscing", "commodo", "elit,", "at", "imperdiet", "dui", "accumsan", "sit",
158 | "amet", "nulla", "facilisi", "morbi", "tempus", "iaculis", "urna,", "id",
159 | "volutpat", "lacus", "laoreet", "non", "curabitur", "gravida", "arcu", "ac",
160 | "tortor", "dignissim", "convallis", "aenean", "et", "tortor", "at", "risus",
161 | "viverra", "adipiscing", "at", "in", "tellus", "integer", "feugiat",
162 | "scelerisque", "varius", "morbi", "enim", "nunc,", "faucibus", "a",
163 | "pellentesque", "sit", "amet,", "porttitor", "eget", "dolor", "morbi", "non",
164 | "arcu", "risus,", "quis", "varius", "quam", "quisque", "id", "diam", "vel",
165 | "quam", "elementum", "pulvinar", "etiam", "non", "quam", "lacus",
166 | "suspendisse", "faucibus", "interdum", "posuere", "lorem", "ipsum", "dolor",
167 | "sit", "amet,", "consectetur", "adipiscing", "elit", "duis", "tristique",
168 | "sollicitudin", "nibh", "sit", "amet", "commodo", "nulla", "facilisi",
169 | "nullam", "vehicula", "ipsum", "a", "arcu", "cursus", "vitae", "congue",
170 | "mauris", "rhoncus", "aenean", "vel", "elit", "scelerisque", "mauris",
171 | "pellentesque", "pulvinar", "pellentesque", "habitant", "morbi", "tristique",
172 | "senectus", "et", "netus", "et", "malesuada", "fames", "ac", "turpis",
173 | "egestas", "maecenas", "pharetra", "convallis", "posuere", "morbi", "leo",
174 | "urna,", "molestie", "at", "elementum", "eu,", "facilisis", "sed", "odio",
175 | "morbi", "quis", "commodo", "odio", "aenean", "sed", "adipiscing", "diam",
176 | "donec", "adipiscing", "tristique", "risus", "nec", "feugiat", "in",
177 | "fermentum", "posuere", "urna", "nec", "tincidunt", "praesent", "semper",
178 | "feugiat", "nibh", "sed", "pulvinar", "proin", "gravida", "hendrerit",
179 | "lectus", "a", "molestie", "gravida", "dictum"
180 | ];
181 |
182 | function randomInt ( min, max ) {
183 | return Math.floor(Math.random() * (max - min + 1)) + min;
184 | }
185 |
186 | return {
187 | createSentence: function ( sentenceLength ) {
188 | var wordIndex,
189 | sentence;
190 |
191 | // Determine how long the sentence should be. Do it randomly if one was not
192 | // provided.
193 | sentenceLength = sentenceLength || randomInt( 5, 20 );
194 |
195 | // Now we determine were we are going to start in the array randomly. We
196 | // are just going to take a slice of the array, so we have to ensure
197 | // whereever we start has enough places left in the array to accommodate
198 | // the random sentence length from above.
199 | wordIndex = randomInt(0, words.length - sentenceLength - 1);
200 |
201 | // And pull out the words, join them together, separating words by spaces
202 | // (duh), and removing any commas that may appear at the end of the
203 | // sentence. Finally, add a period.
204 | sentence = words.slice(wordIndex, wordIndex + sentenceLength)
205 | .join(' ')
206 | .replace(/\,$/g, '') + '.';
207 |
208 | // Capitalize the first letter - it is a sentence, after all.
209 | sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);
210 |
211 | return sentence;
212 | },
213 | createSentences: function ( numSentences ) {
214 | var sentences = [],
215 | i = 0;
216 |
217 | // Determine how many sentences we should do. Do it randomly if one was not
218 | // provided.
219 | numSentences = numSentences || randomInt( 3, 5 );
220 |
221 | // For each paragraph, we should generate between 3 and 5 sentences.
222 | for ( i = 0; i < numSentences; i++ ) {
223 | sentences.push( this.createSentence() );
224 | }
225 |
226 | // And then we just return the array of sentences, concatenated with spaces.
227 | return sentences.join( ' ' );
228 | },
229 | createParagraph: function ( numSentences ) {
230 | var sentences = this.createSentences( numSentences );
231 |
232 | // Make the sentences into a paragraph and return.
233 | return "
" + sentences + "
";
234 | },
235 | createParagraphs: function ( numParagraphs, numSentences ) {
236 | var paragraphs = [],
237 | i = 0;
238 |
239 | numParagraphs = numParagraphs || randomInt( 3, 7 );
240 |
241 | // Create the number of paragraphs requested.
242 | for ( i = 0; i < numParagraphs; i++ ) {
243 | paragraphs.push( this.createParagraph( numSentences ) );
244 | }
245 |
246 | // Return the paragraphs, concatenated with newlines.
247 | return paragraphs.join( '\n' );
248 | }
249 | };
250 | })
251 |
252 | .directive( 'phTxt', [ 'TextGeneratorService', function ( TextGeneratorService ) {
253 | return {
254 | restrict: "EA",
255 | controller: [ '$scope', '$element', '$attrs', function ( $scope, $element, $attrs ) {
256 | var numSentences,
257 | numParagraphs;
258 |
259 | // Gets the number of paragraphs or sentences from the service and
260 | // populates the DOM node.
261 | function populate() {
262 | var contents;
263 |
264 | // If p or neither, then get paragraphs. Else, get sentences.
265 | if ( numParagraphs || !numSentences ) {
266 | contents = TextGeneratorService.createParagraphs( numParagraphs, numSentences );
267 | } else {
268 | contents = TextGeneratorService.createSentences( numSentences );
269 | }
270 |
271 | $element.html( contents );
272 | }
273 |
274 | $attrs.$observe( 'phTxt', function ( val ) {
275 | var p_match, s_match;
276 |
277 | // Pull out the matches.
278 | p_match = val.match( /(\d+)p/ );
279 | s_match = val.match( /(\d+)s/ );
280 |
281 | // If there was a match, store the value. If there wasn't, we set the
282 | // value to false to ensure no old value is kept around.
283 | if ( p_match !== null ) {
284 | numParagraphs = parseInt( p_match[1], 10 );
285 | } else {
286 | numParagraphs = false;
287 | }
288 |
289 | // Same for sentences...
290 | if ( s_match !== null ) {
291 | numSentences = parseInt( s_match[1], 10 );
292 | } else {
293 | numSentences = false;
294 | }
295 |
296 | // And populate everything.
297 | populate();
298 | });
299 |
300 | // If nothing was passed, the $observe will never run, so we need to trigger
301 | // the `populate()` manually.
302 | if ( ! $attrs.phTxt ) {
303 | populate();
304 | }
305 | }]
306 | };
307 | }]);
308 |
309 |
--------------------------------------------------------------------------------