<%- post.title %>
++ <% if (post.excerpt){ %> + <%- post.excerpt %> + <% } else { %> + <%- post.content.replace(/<(?:.|\n)*?>/gm, '').substr(0, 220) %> + <% } %> +
+ <% if (theme.excerpt_link) { %> + + <% } %> +diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml deleted file mode 100644 index 660053a36..000000000 --- a/.github/workflows/create-release.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This is a basic workflow that is manually triggered - -name: Create Release - -# Controls when the action will run. Workflow runs when manually triggered using the UI -# or API. -on: - workflow_dispatch: - # Inputs the workflow accepts. - inputs: - release: - description: 'Release Version' - required: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - create_release: - # The type of runner that the job will run on - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - RELEASE: ${{ github.event.inputs.release || github.ref_name }} - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Checkout - uses: actions/checkout@v3.3.0 - with: - ref: gh-pages - - - name: Zip Release - uses: TheDoctor0/zip-release@0.7.0 - with: - filename: ui-grid-${{ env.RELEASE }}.zip - path: ./release/${{ env.RELEASE }}/* - - - name: Publish Release - run: gh release create v${{ env.RELEASE }} ./ui-grid-${{ env.RELEASE }}.zip - diff --git a/.gitignore b/.gitignore index de9383995..d8a17729a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.sublime-* \ No newline at end of file +*.sublime-* +node_modules \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..ed509de49 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +ui-grid.info diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..75a496104 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,59 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + shell: { + hexogen: { + command: 'hexo generate', + options: { + execOptions: { + cwd: '_blog_src' + } + } + } + }, + + stylus: { + compile: { + options: { + compress: true + }, + files: { + 'blog/css/style.css': '_blog_src/themes/casper_custom/source/css/style.styl' + } + } + }, + + connect: { + server: { + options: { + port: 4000, + base: '.', + livereload: true + } + } + }, + + watch: { + hexo: { + files: ['_blog_src/_config.yml', '_blog_src/themes/**', '_blog_src/scaffolds/**', '_blog_src/scripts/**', '_blog_src/source/**'], + tasks: ['shell:hexogen'] + }, + livereload: { + options: { livereload: true }, + files: ['blog/**'], + } + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-stylus'); + grunt.loadNpmTasks('grunt-shell'); + + // Default task. + grunt.registerTask('dev', ['shell:hexogen', 'connect', 'watch']); + grunt.registerTask('default', ['shell:hexogen']); +}; diff --git a/README.md b/README.md index 55d15a3c1..67b21ce63 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ui-grid.info ============ -Website for ui-grid: http://ui-grid.info. +Website for ui-grid -The source code in gh-pages is created from https://github.com/angular-ui/ui-grid during the build process. The actual source is located on https://github.com/angular-ui/ui-grid so any issues should be raised in that repository. +[Using the blog system](_blog_src/Blogging.md) \ No newline at end of file diff --git a/_blog_src/.gitignore b/_blog_src/.gitignore new file mode 100644 index 000000000..a7640be62 --- /dev/null +++ b/_blog_src/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +Thumbs.db +db.json +debug.log +node_modules/ +public/ +.deploy/ \ No newline at end of file diff --git a/_blog_src/Blogging.md b/_blog_src/Blogging.md new file mode 100644 index 000000000..b415c6869 --- /dev/null +++ b/_blog_src/Blogging.md @@ -0,0 +1,27 @@ + +# UI-Grid.info Blog + +The "blog" uses [hexo](http://hexo.io) to generate a pretty simple static site. + +## Installing + +To use it you need to install hexo: + + npm install -g hexo@2.8.3 + +Then install dependencies in both the parent and _blog_src directories. This needs to be done bcause hexo's cwd needs to be the blog source directory. + + npm install + cd _blog_src + npm install + +Then go back up and run the dev task: + + cd .. + grund dev + +You'll get a server running on [http://localhost:4000](http://localhost:4000) with auto-watch on the blog directory to rebuild when you change files. + +## Creating Posts + +New posts can be created in `_blog_src/source/_posts` or by running `hexo new` in _blog_src. You can find further documentation on hexo's site: http://hexo.io \ No newline at end of file diff --git a/_blog_src/Gruntfile.js b/_blog_src/Gruntfile.js new file mode 100644 index 000000000..9a1a5447a --- /dev/null +++ b/_blog_src/Gruntfile.js @@ -0,0 +1,54 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + shell: { + hexogen: { + command: 'hexo generate' + } + }, + + stylus: { + compile: { + options: { + compress: true + }, + files: { + 'public/css/style.css': 'themes/landscape/source/css/style.styl' + } + } + }, + + connect: { + server: { + options: { + port: 4000, + base: 'public', + livereload: true + } + } + }, + + watch: { + hexo: { + files: ['_config.yml', 'themes/**', 'scaffolds/**', 'scripts/**', 'source/**'], + tasks: ['shell:hexogen'] + }, + livereload: { + options: { livereload: true }, + files: ['public/**'], + } + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-stylus'); + grunt.loadNpmTasks('grunt-shell'); + + // Default task. + grunt.registerTask('dev', ['shell:hexogen', 'connect', 'watch']); + +}; diff --git a/_blog_src/_config.yml b/_blog_src/_config.yml new file mode 100644 index 000000000..c0e000a99 --- /dev/null +++ b/_blog_src/_config.yml @@ -0,0 +1,109 @@ +# Hexo Configuration +## Docs: http://hexo.io/docs/configuration.html +## Source: https://github.com/tommy351/hexo/ + +# Site +# UI-Grid Blog +title: UI Grid +subtitle: An AngularJS data grid +description: "Tips, tricks and news for UI Grid" +author: UI Grid Team +email: uigridteam@gmail.com +language: + +# URL +## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/' +url: http://ui-grid.info/blog +root: /blog/ +permalink: :title/ +tag_dir: tags +archive_dir: archives +category_dir: categories +code_dir: downloads/code + +# Directory +source_dir: source +public_dir: ../blog + +# Writing +new_post_name: :title.md # File name of new posts +default_layout: post +auto_spacing: false # Add spaces between asian characters and western characters +titlecase: false # Transform title into titlecase +external_link: true # Open external links in new tab +max_open_file: 100 +multi_thread: true +filename_case: 0 +render_drafts: false +post_asset_folder: false +highlight: + enable: true + line_number: true + tab_replace: + +# Category & Tag +default_category: uncategorized +category_map: +tag_map: + +# Archives +## 2: Enable pagination +## 1: Disable pagination +## 0: Fully Disable +archive: 2 +category: 1 +tag: 1 + +# Server +## Hexo uses Connect as a server +## You can customize the logger format as defined in +## http://www.senchalabs.org/connect/logger.html +port: 4000 +server_ip: 0.0.0.0 +logger: false +logger_format: + +# Date / Time format +## Hexo uses Moment.js to parse and display date +## You can customize the date format as defined in +## http://momentjs.com/docs/#/displaying/format/ +date_format: MMM D YYYY +time_format: H:mm:ss + +# Pagination +## Set per_page to 0 to disable pagination +per_page: 10 +pagination_dir: page + +# Disqus +disqus_shortname: + +# Extensions +## Plugins: https://github.com/tommy351/hexo/wiki/Plugins +## Themes: https://github.com/tommy351/hexo/wiki/Themes +theme: casper_custom +exclude_generator: + +# Deployment +## Docs: http://hexo.io/docs/deployment.html +deploy: + type: + +sitemap: + path: ../sitemap.xml + +# casper theme config +cover: +logo: +bio: + +# Content +excerpt_link: Read More + +# Miscellaneous +rss: + +feed: + type: atom + path: atom.xml + limit: 20 diff --git a/_blog_src/package.json b/_blog_src/package.json new file mode 100644 index 000000000..b35b8ba1d --- /dev/null +++ b/_blog_src/package.json @@ -0,0 +1,12 @@ +{ + "name": "ui-grid.info.blog", + "version": "2.8.3", + "private": true, + "dependencies": { + "hexo-renderer-ejs": "*", + "hexo-renderer-stylus": "0.1.x", + "hexo-renderer-marked": "0.1.x", + "hexo-generator-cherrypick": "https://github.com/c0bra/hexo-generator-cherrypick/tarball/0.0.1", + "hexo-generator-feed": "^0.2.1" + } +} diff --git a/_blog_src/scaffolds/draft.md b/_blog_src/scaffolds/draft.md new file mode 100644 index 000000000..45b1bb754 --- /dev/null +++ b/_blog_src/scaffolds/draft.md @@ -0,0 +1,3 @@ +title: {{ title }} +tags: +--- diff --git a/_blog_src/scaffolds/page.md b/_blog_src/scaffolds/page.md new file mode 100644 index 000000000..f484b7615 --- /dev/null +++ b/_blog_src/scaffolds/page.md @@ -0,0 +1,3 @@ +title: {{ title }} +date: {{ date }} +--- diff --git a/_blog_src/scaffolds/photo.md b/_blog_src/scaffolds/photo.md new file mode 100644 index 000000000..5aba0db62 --- /dev/null +++ b/_blog_src/scaffolds/photo.md @@ -0,0 +1,5 @@ +layout: {{ layout }} +title: {{ title }} +date: {{ date }} +tags: +--- diff --git a/_blog_src/scaffolds/post.md b/_blog_src/scaffolds/post.md new file mode 100644 index 000000000..c590d7a77 --- /dev/null +++ b/_blog_src/scaffolds/post.md @@ -0,0 +1,4 @@ +title: {{ title }} +date: {{ date }} +tags: +--- diff --git a/_blog_src/source/_posts/6_ways_to_take_control_of_how_your_grid_data_is_displayed.md b/_blog_src/source/_posts/6_ways_to_take_control_of_how_your_grid_data_is_displayed.md new file mode 100644 index 000000000..add58ae69 --- /dev/null +++ b/_blog_src/source/_posts/6_ways_to_take_control_of_how_your_grid_data_is_displayed.md @@ -0,0 +1,132 @@ +author: Brian +title: 6 Ways to Take Control of How Your Grid Data is Displayed +date: 2015-02-27 10:55:01 +tags: + - customize + - display +--- + + + +Getting your data displayed just right can be a huge pain. + +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. + +In this post outline six different methods you can use to get your data **just the way you want it**: + +* Bindings +* Cell Filters +* Cell Templates + * Links + * Buttons + * Custom directives + +## Bindings + +UI Grid columns can be bound to any sort of Angular expression, as well as property names that would normally break an expression: + +{% codeblock lang:js %} +var columnDefs = [ + { field: 'name' }, + { field: 'addresses[0]' }, + + /* + Yes, you can use hyphens, plus signs, etc. + + Normally something like {% raw %}{{ row.entity.first-name }}{% endraw %} would + bomb but UI Grid pre-processes your bindings to make sure they work correctly. + */ + { field: 'first-name' }, + + /* Function expressions work too */ + { field: 'getCurrency()' }, + { field: 'transformValue(row.entity.myField)' }, + + /* Plain old functions are also an option */ + { field: function () { return this.address.zip; } } +]; +{% endcodeblock %} + +## Cell Filters + +Cell filters can transform the displayed value for a column while leaving the model intact. In this plunker the amount column contains floating point values, but we only want to display the integer part. A simple cellFilter that uses `.toFixed()` will alter the displayed value the way we want. + +{% iframe http://embed.plnkr.co/BjLqXGiUI8nQFwvijduh/preview %} + +### Passing Arguments ### + +In the third column I am using two different fields. The column itself is bound to the same field as the second column, but on the filter I am passing the scope like so: `cellFilter: 'currencyFilter:this'`. Using the `this` argument on your filter will pass the scope. + +Then I can lookup the currency symbol I want using the currency field from the row. + +If you double-click a cell in the Currency column you'll see that the raw value is still available to be edited. Cell filters only change your displayed value! + +## Cell Templates ## + +Column definitions take a `cellTemplate` argument you can use to give your cells a custom template. You can specify it a few different ways, with a url (relative or absolute), an Angular template ID, a string/Angular element, or a promise. + +{% codeblock lang:js %} +var columnDefs = [ + { field: 'name', cellTemplate: 'name-template.html' }, + { field: 'name', cellTemplate: 'myTemplateId' }, + { field: 'name', cellTemplate: $.get('url-to-your-template.html') } +]; +{% endcodeblock %} + +### Bindings in Cell Templates ### + +There are a couple options for your binding your row's data in your template. You can access the `row` object which is in the local scope; `row.entity` will contain the reference to your object, so if you want the "name" field, you can bind like so: {{ row.entity.name }}. + +If you've *already* defined your binding in your column definition you can use one of UI Grid's placeholders: `{% raw %}{{ COL_FIELD }}{% endraw %}`. UI Grid will automatically replace COL_FIELD with the appropriate binding. If you need to two-way bind to your row, like when you're using the edit feature, you'll need to use the `MODEL_COL_FIELD` placeholder. + +{% codeblock lang:js %} +var columnDefs = [ + { + field: 'image_url', + cellTemplate: '
+ <% if (post.excerpt){ %> + <%- post.excerpt %> + <% } else { %> + <%- post.content.replace(/<(?:.|\n)*?>/gm, '').substr(0, 220) %> + <% } %> +
+ <% if (theme.excerpt_link) { %> + + <% } %> ++ <% if (post.excerpt){ %> + <%- post.excerpt %> + <% } else { %> + <%- truncate(post.content, { length: 220, separator: ' '}) %> + <% } %> +
++ <% if (author) { %> + Posted by <%= author %> on <%= post.date.format('MMMM Do YYYY') %> + <% } %> + <% if (theme.excerpt_link) { %> + + <% } %> +
+The requested content cannot be loaded.
Please try again later.
The requested content cannot be loaded.
Please try again later.
Getting your data displayed just right can be a huge pain.
+You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive.
+In this post outline six different methods you can use to get your data just the way you want it:
+UI Grid columns can be bound to any sort of Angular expression, as well as property names that would normally break an expression:
+
|
|
Cell filters can transform the displayed value for a column while leaving the model intact. In this plunker the amount column contains floating point values, but we only want to display the integer part. A simple cellFilter that uses .toFixed()
will alter the displayed value the way we want.
In the third column I am using two different fields. The column itself is bound to the same field as the second column, but on the filter I am passing the scope like so: cellFilter: 'currencyFilter:this'
. Using the this
argument on your filter will pass the scope.
Then I can lookup the currency symbol I want using the currency field from the row.
+If you double-click a cell in the Currency column you’ll see that the raw value is still available to be edited. Cell filters only change your displayed value!
+Column definitions take a cellTemplate
argument you can use to give your cells a custom template. You can specify it a few different ways, with a url (relative or absolute), an Angular template ID, a string/Angular element, or a promise.
|
|
There are a couple options for your binding your row’s data in your template. You can access the row
object which is in the local scope; row.entity
will contain the reference to your object, so if you want the “name” field, you can bind like so: .
If you’ve already defined your binding in your column definition you can use one of UI Grid’s placeholders: {{ COL_FIELD }}
. UI Grid will automatically replace COL_FIELD with the appropriate binding. If you need to two-way bind to your row, like when you’re using the edit feature, you’ll need to use the MODEL_COL_FIELD
placeholder.
|
|
Links are simple, just use regular old HTML. Wrapping your cell in an element using the class .ui-grid-cell-contents
will apply the proper CSS settings like padding, overflow, etc., and make sure your cell fits in its space nicely.
|
|
In the plunker below I’m using a basic cellTemplate to bind both the first and last name fields into one column. On the second column I’m using a simple link like above (note: none of these emails are real).
+On the third one, I’m using a template that’s referenced in index.html. If you check the bottom of that file you’ll see the template inside a <script type="text/ng-template">
tag. Any tags of that type will put automatically put into the template cache ($templateCache
) by Angular, with the id you specify.
Also in the third column template you can see I’m using a variable called grid.appScope
. Anywhere in your templates, grid.appScope
will be bound to the parent scope of your grid. I use it to bind my button to a function in my controller that will open a new window with a url to google maps with the address in it (note: most if not all of these address will not exist).
Tooltips are a little bit different because they float over your content. By default the grid’s cells are set to hide any overflow, so if you just pop a tooltip in there it won’t show up and you’ll probably be very confused. If you just add some custom overflow CSS it should fix your problem.
+Hover over the names in the plunker below:
+ + +Tooltips cannot overflow beyond the constraints of the viewport, so they won’t show above the first row or below the bottom row, for example.
+You can put absolutely anything in your cell templates, just remember to use .ui-grid-cell-contents
if you’re not applying your own custom cell CSS.
Here I’ve used d3.js, nvd3.js, and angular-nvd3.js to create sparkline charts in my cells.
+You should be able to do the vast majority of what you want with your data using these six methods. If you have questions, suggestions, or just want to say hi, drop in to our gitter channel; we’d love to meet you!
+ + + ++ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ Read More... +
+ ++ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ Read More... +
+ ++ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ Read More... +
+ +Getting your data displayed just right can be a huge pain.
+You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive.
+In this post outline six different methods you can use to get your data just the way you want it:
+UI Grid columns can be bound to any sort of Angular expression, as well as property names that would normally break an expression:
+
|
|
Cell filters can transform the displayed value for a column while leaving the model intact. In this plunker the amount column contains floating point values, but we only want to display the integer part. A simple cellFilter that uses .toFixed()
will alter the displayed value the way we want.
In the third column I am using two different fields. The column itself is bound to the same field as the second column, but on the filter I am passing the scope like so: cellFilter: 'currencyFilter:this'
. Using the this
argument on your filter will pass the scope.
Then I can lookup the currency symbol I want using the currency field from the row.
+If you double-click a cell in the Currency column you’ll see that the raw value is still available to be edited. Cell filters only change your displayed value!
+Column definitions take a cellTemplate
argument you can use to give your cells a custom template. You can specify it a few different ways, with a url (relative or absolute), an Angular template ID, a string/Angular element, or a promise.
|
|
There are a couple options for your binding your row’s data in your template. You can access the row
object which is in the local scope; row.entity
will contain the reference to your object, so if you want the “name” field, you can bind like so: .
If you’ve already defined your binding in your column definition you can use one of UI Grid’s placeholders: {{ COL_FIELD }}
. UI Grid will automatically replace COL_FIELD with the appropriate binding. If you need to two-way bind to your row, like when you’re using the edit feature, you’ll need to use the MODEL_COL_FIELD
placeholder.
|
|
Links are simple, just use regular old HTML. Wrapping your cell in an element using the class .ui-grid-cell-contents
will apply the proper CSS settings like padding, overflow, etc., and make sure your cell fits in its space nicely.
|
|
In the plunker below I’m using a basic cellTemplate to bind both the first and last name fields into one column. On the second column I’m using a simple link like above (note: none of these emails are real).
+On the third one, I’m using a template that’s referenced in index.html. If you check the bottom of that file you’ll see the template inside a <script type="text/ng-template">
tag. Any tags of that type will put automatically put into the template cache ($templateCache
) by Angular, with the id you specify.
Also in the third column template you can see I’m using a variable called grid.appScope
. Anywhere in your templates, grid.appScope
will be bound to the parent scope of your grid. I use it to bind my button to a function in my controller that will open a new window with a url to google maps with the address in it (note: most if not all of these address will not exist).
Tooltips are a little bit different because they float over your content. By default the grid’s cells are set to hide any overflow, so if you just pop a tooltip in there it won’t show up and you’ll probably be very confused. If you just add some custom overflow CSS it should fix your problem.
+Hover over the names in the plunker below:
+ + +Tooltips cannot overflow beyond the constraints of the viewport, so they won’t show above the first row or below the bottom row, for example.
+You can put absolutely anything in your cell templates, just remember to use .ui-grid-cell-contents
if you’re not applying your own custom cell CSS.
Here I’ve used d3.js, nvd3.js, and angular-nvd3.js to create sparkline charts in my cells.
+You should be able to do the vast majority of what you want with your data using these six methods. If you have questions, suggestions, or just want to say hi, drop in to our gitter channel; we’d love to meet you!
+]]>Getting your data displayed just right can be a huge pain.
+You might just want to]]> +
+ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ + +
Getting your data displayed just right can be a huge pain.
+You might just want to format some numbers, or you might want to embed something complex like a... + +
++ + Posted by Brian on February 27th 2015 + + + + +
++ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ Read More... +
+ ++ + +Getting your data displayed just right can be a huge pain. +You might just want to format some numbers, or you might want to embed something complex like a chart or custom directive. +In this post outline six different me + +
+ ++ Read More... +
+ +'
+ + escape(cap[2], true)
+ + '
';
+ continue;
+ }
+
+ // br
+ if (cap = this.rules.br.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += ''
+ + this.token.text
+ + '
\n';
+ }
+ case 'table': {
+ var body = ''
+ , heading
+ , i
+ , row
+ , cell
+ , j;
+
+ // header
+ body += '\n\n' + + body + + '\n'; + } + case 'list_start': { + var type = this.token.ordered ? 'ol' : 'ul' + , body = ''; + + while (this.next().type !== 'list_end') { + body += this.tok(); + } + + return '<' + + type + + '>\n' + + body + + '' + + type + + '>\n'; + } + case 'list_item_start': { + var body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.token.type === 'text' + ? this.parseText() + : this.tok(); + } + + return '
' + + this.inline.output(this.token.text) + + '
\n'; + } + case 'text': { + return '' + + this.parseText() + + '
\n'; + } + } +}; + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function replace(regex, opt) { + regex = regex.source; + opt = opt || ''; + return function self(name, val) { + if (!name) return new RegExp(regex, opt); + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return self; + }; +} + +function noop() {} +noop.exec = noop; + +function merge(obj) { + var i = 1 + , target + , key; + + for (; i < arguments.length; i++) { + target = arguments[i]; + for (key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + obj[key] = target[key]; + } + } + } + + return obj; +} + +/** + * Marked + */ + +function marked(src, opt, callback) { + if (callback || typeof opt === 'function') { + if (!callback) { + callback = opt; + opt = null; + } + + opt = merge({}, marked.defaults, opt || {}); + + var highlight = opt.highlight + , tokens + , pending + , i = 0; + + try { + tokens = Lexer.lex(src, opt) + } catch (e) { + return callback(e); + } + + pending = tokens.length; + + var done = function() { + var out, err; + + try { + out = Parser.parse(tokens, opt); + } catch (e) { + err = e; + } + + opt.highlight = highlight; + + return err + ? callback(err) + : callback(null, out); + }; + + if (!highlight || highlight.length < 3) { + return done(); + } + + delete opt.highlight; + + if (!pending) return done(); + + for (; i < tokens.length; i++) { + (function(token) { + if (token.type !== 'code') { + return --pending || done(); + } + return highlight(token.text, token.lang, function(err, code) { + if (code == null || code === token.text) { + return --pending || done(); + } + token.text = code; + token.escaped = true; + --pending || done(); + }); + })(tokens[i]); + } + + return; + } + try { + if (opt) opt = merge({}, marked.defaults, opt); + return Parser.parse(Lexer.lex(src, opt), opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/chjj/marked.'; + if ((opt || marked.defaults).silent) { + return 'An error occured:
' + + escape(e.message + '', true) + + ''; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.defaults = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: false, + smartLists: false, + silent: false, + highlight: null, + langPrefix: 'lang-', + smartypants: false +}; + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; + +if (typeof exports === 'object') { + module.exports = marked; +} else if (typeof define === 'function' && define.amd) { + define(function() { return marked; }); +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); diff --git a/docs/grunt-scripts/pdfmake.js b/docs/grunt-scripts/pdfmake.js new file mode 100644 index 000000000..1fe810d4d --- /dev/null +++ b/docs/grunt-scripts/pdfmake.js @@ -0,0 +1,32944 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pdfMake = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
' + func(text) + '
'; + * }); + * + * p('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles
' + */ + function wrap(value, wrapper) { + wrapper = wrapper == null ? identity : wrapper; + return createWrapper(wrapper, PARTIAL_FLAG, null, [value], []); + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned, + * otherwise they are assigned by reference. If `customizer` is provided it is + * invoked to produce the cloned values. If `customizer` returns `undefined` + * cloning is handled by the method instead. The `customizer` is bound to + * `thisArg` and invoked with two argument; (value [, index|key, object]). + * + * **Note:** This method is loosely based on the structured clone algorithm. + * The enumerable properties of `arguments` objects and objects created by + * constructors other than `Object` are cloned to plain `Object` objects. An + * empty object is returned for uncloneable values such as functions, DOM nodes, + * Maps, Sets, and WeakMaps. See the [HTML5 specification](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm) + * for more details. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @param {Function} [customizer] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {*} Returns the cloned value. + * @example + * + * var users = [ + * { 'user': 'barney' }, + * { 'user': 'fred' } + * ]; + * + * var shallow = _.clone(users); + * shallow[0] === users[0]; + * // => true + * + * var deep = _.clone(users, true); + * deep[0] === users[0]; + * // => false + * + * // using a customizer callback + * var body = _.clone(document.body, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }); + * + * body === document.body + * // => false + * body.nodeName + * // => BODY + * body.childNodes.length; + * // => 0 + */ + function clone(value, isDeep, customizer, thisArg) { + // Juggle arguments. + if (typeof isDeep != 'boolean' && isDeep != null) { + thisArg = customizer; + customizer = isIterateeCall(value, isDeep, thisArg) ? null : isDeep; + isDeep = false; + } + customizer = typeof customizer == 'function' && bindCallback(customizer, thisArg, 1); + return baseClone(value, isDeep, customizer); + } + + /** + * Creates a deep clone of `value`. If `customizer` is provided it is invoked + * to produce the cloned values. If `customizer` returns `undefined` cloning + * is handled by the method instead. The `customizer` is bound to `thisArg` + * and invoked with two argument; (value [, index|key, object]). + * + * **Note:** This method is loosely based on the structured clone algorithm. + * The enumerable properties of `arguments` objects and objects created by + * constructors other than `Object` are cloned to plain `Object` objects. An + * empty object is returned for uncloneable values such as functions, DOM nodes, + * Maps, Sets, and WeakMaps. See the [HTML5 specification](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm) + * for more details. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to deep clone. + * @param {Function} [customizer] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {*} Returns the deep cloned value. + * @example + * + * var users = [ + * { 'user': 'barney' }, + * { 'user': 'fred' } + * ]; + * + * var deep = _.cloneDeep(users); + * deep[0] === users[0]; + * // => false + * + * // using a customizer callback + * var el = _.cloneDeep(document.body, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : undefined; + * }); + * + * body === document.body + * // => false + * body.nodeName + * // => BODY + * body.childNodes.length; + * // => 20 + */ + function cloneDeep(value, customizer, thisArg) { + customizer = typeof customizer == 'function' && bindCallback(customizer, thisArg, 1); + return baseClone(value, true, customizer); + } + + /** + * Checks if `value` is classified as an `arguments` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + var length = isObjectLike(value) ? value.length : undefined; + return (isLength(length) && objToString.call(value) == argsTag) || false; + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * (function() { return _.isArray(arguments); })(); + * // => false + */ + var isArray = nativeIsArray || function(value) { + return (isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag) || false; + }; + + /** + * Checks if `value` is classified as a boolean primitive or object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isBoolean(false); + * // => true + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return (value === true || value === false || isObjectLike(value) && objToString.call(value) == boolTag) || false; + } + + /** + * Checks if `value` is classified as a `Date` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false + */ + function isDate(value) { + return (isObjectLike(value) && objToString.call(value) == dateTag) || false; + } + + /** + * Checks if `value` is a DOM element. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + * + * _.isElement(''); + * // => false + */ + function isElement(value) { + return (value && value.nodeType === 1 && isObjectLike(value) && + objToString.call(value).indexOf('Element') > -1) || false; + } + // Fallback for environments without DOM support. + if (!support.dom) { + isElement = function(value) { + return (value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value)) || false; + }; + } + + /** + * Checks if a value is empty. A value is considered empty unless it is an + * `arguments` object, array, string, or jQuery-like collection with a length + * greater than `0` or an object with own enumerable properties. + * + * @static + * @memberOf _ + * @category Lang + * @param {Array|Object|string} value The value to inspect. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (value == null) { + return true; + } + var length = value.length; + if (isLength(length) && (isArray(value) || isString(value) || isArguments(value) || + (isObjectLike(value) && isFunction(value.splice)))) { + return !length; + } + return !keys(value).length; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. If `customizer` is provided it is invoked to compare values. + * If `customizer` returns `undefined` comparisons are handled by the method + * instead. The `customizer` is bound to `thisArg` and invoked with three + * arguments; (value, other [, index|key]). + * + * **Note:** This method supports comparing arrays, booleans, `Date` objects, + * numbers, `Object` objects, regexes, and strings. Functions and DOM nodes + * are **not** supported. Provide a customizer function to extend support + * for comparing other values. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparing values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'user': 'fred' }; + * var other = { 'user': 'fred' }; + * + * object == other; + * // => false + * + * _.isEqual(object, other); + * // => true + * + * // using a customizer callback + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * _.isEqual(array, other, function(value, other) { + * return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined; + * }); + * // => true + */ + function isEqual(value, other, customizer, thisArg) { + customizer = typeof customizer == 'function' && bindCallback(customizer, thisArg, 3); + if (!customizer && isStrictComparable(value) && isStrictComparable(other)) { + return value === other; + } + var result = customizer ? customizer(value, other) : undefined; + return typeof result == 'undefined' ? baseIsEqual(value, other, customizer) : !!result; + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + return (isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag) || false; + } + + /** + * Checks if `value` is a finite primitive number. + * + * **Note:** This method is based on ES `Number.isFinite`. See the + * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isfinite) + * for more details. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. + * @example + * + * _.isFinite(10); + * // => true + * + * _.isFinite('10'); + * // => false + * + * _.isFinite(true); + * // => false + * + * _.isFinite(Object(10)); + * // => false + * + * _.isFinite(Infinity); + * // => false + */ + var isFinite = nativeNumIsFinite || function(value) { + return typeof value == 'number' && nativeIsFinite(value); + }; + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // Avoid a Chakra JIT bug in compatibility modes of IE 11. + // See https://github.com/jashkenas/underscore/issues/1621 for more details. + return typeof value == 'function' || false; + } + // Fallback for environments that return incorrect `typeof` operator results. + if (isFunction(/x/) || (Uint8Array && !isFunction(Uint8Array))) { + isFunction = function(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return objToString.call(value) == funcTag; + }; + } + + /** + * Checks if `value` is the language type of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * **Note:** See the [ES5 spec](https://es5.github.io/#x8) for more details. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ + function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return type == 'function' || (value && type == 'object') || false; + } + + /** + * Performs a deep comparison between `object` and `source` to determine if + * `object` contains equivalent property values. If `customizer` is provided + * it is invoked to compare values. If `customizer` returns `undefined` + * comparisons are handled by the method instead. The `customizer` is bound + * to `thisArg` and invoked with three arguments; (value, other, index|key). + * + * **Note:** This method supports comparing properties of arrays, booleans, + * `Date` objects, numbers, `Object` objects, regexes, and strings. Functions + * and DOM nodes are **not** supported. Provide a customizer function to extend + * support for comparing other values. + * + * @static + * @memberOf _ + * @category Lang + * @param {Object} source The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Function} [customizer] The function to customize comparing values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * var object = { 'user': 'fred', 'age': 40 }; + * + * _.isMatch(object, { 'age': 40 }); + * // => true + * + * _.isMatch(object, { 'age': 36 }); + * // => false + * + * // using a customizer callback + * var object = { 'greeting': 'hello' }; + * var source = { 'greeting': 'hi' }; + * + * _.isMatch(object, source, function(value, other) { + * return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined; + * }); + * // => true + */ + function isMatch(object, source, customizer, thisArg) { + var props = keys(source), + length = props.length; + + customizer = typeof customizer == 'function' && bindCallback(customizer, thisArg, 3); + if (!customizer && length == 1) { + var key = props[0], + value = source[key]; + + if (isStrictComparable(value)) { + return object != null && value === object[key] && hasOwnProperty.call(object, key); + } + } + var values = Array(length), + strictCompareFlags = Array(length); + + while (length--) { + value = values[length] = source[props[length]]; + strictCompareFlags[length] = isStrictComparable(value); + } + return baseIsMatch(object, props, values, strictCompareFlags, customizer); + } + + /** + * Checks if `value` is `NaN`. + * + * **Note:** This method is not the same as native `isNaN` which returns `true` + * for `undefined` and other non-numeric values. See the [ES5 spec](https://es5.github.io/#x15.1.2.4) + * for more details. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // An `NaN` primitive is the only value that is not equal to itself. + // Perform the `toStringTag` check first to avoid errors with some host objects in IE. + return isNumber(value) && value != +value; + } + + /** + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ + function isNative(value) { + if (value == null) { + return false; + } + if (objToString.call(value) == funcTag) { + return reNative.test(fnToString.call(value)); + } + return (isObjectLike(value) && reHostCtor.test(value)) || false; + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(void 0); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is classified as a `Number` primitive or object. + * + * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are classified + * as numbers, use the `_.isFinite` method. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isNumber(8.4); + * // => true + * + * _.isNumber(NaN); + * // => true + * + * _.isNumber('8.4'); + * // => false + */ + function isNumber(value) { + return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag) || false; + } + + /** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * **Note:** This method assumes objects created by the `Object` constructor + * have no inherited enumerable properties. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ + var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { + if (!(value && objToString.call(value) == objectTag)) { + return false; + } + var valueOf = value.valueOf, + objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + + return objProto + ? (value == objProto || getPrototypeOf(value) == objProto) + : shimIsPlainObject(value); + }; + + /** + * Checks if `value` is classified as a `RegExp` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isRegExp(/abc/); + * // => true + * + * _.isRegExp('/abc/'); + * // => false + */ + function isRegExp(value) { + return (isObjectLike(value) && objToString.call(value) == regexpTag) || false; + } + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag) || false; + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + function isTypedArray(value) { + return (isObjectLike(value) && isLength(value.length) && typedArrayTags[objToString.call(value)]) || false; + } + + /** + * Checks if `value` is `undefined`. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + * + * _.isUndefined(null); + * // => false + */ + function isUndefined(value) { + return typeof value == 'undefined'; + } + + /** + * Converts `value` to an array. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Array} Returns the converted array. + * @example + * + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3); + * // => [2, 3] + */ + function toArray(value) { + var length = value ? value.length : 0; + if (!isLength(length)) { + return values(value); + } + if (!length) { + return []; + } + return arrayCopy(value); + } + + /** + * Converts `value` to a plain object flattening inherited enumerable + * properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ + function toPlainObject(value) { + return baseCopy(value, keysIn(value)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources overwrite property assignments of previous sources. + * If `customizer` is provided it is invoked to produce the assigned values. + * The `customizer` is bound to `thisArg` and invoked with five arguments; + * (objectValue, sourceValue, key, object, source). + * + * @static + * @memberOf _ + * @alias extend + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @param {Function} [customizer] The function to customize assigning values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {Object} Returns `object`. + * @example + * + * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' }); + * // => { 'user': 'fred', 'age': 40 } + * + * // using a customizer callback + * var defaults = _.partialRight(_.assign, function(value, other) { + * return typeof value == 'undefined' ? other : value; + * }); + * + * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' }); + * // => { 'user': 'barney', 'age': 36 } + */ + var assign = createAssigner(baseAssign); + + /** + * Creates an object that inherits from the given `prototype` object. If a + * `properties` object is provided its own enumerable properties are assigned + * to the created object. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties, guard) { + var result = baseCreate(prototype); + if (guard && isIterateeCall(prototype, properties, guard)) { + properties = null; + } + return properties ? baseCopy(properties, result, keys(properties)) : result; + } + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object for all destination properties that resolve to `undefined`. Once a + * property is set, additional defaults of the same property are ignored. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @example + * + * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' }); + * // => { 'user': 'barney', 'age': 36 } + */ + function defaults(object) { + if (object == null) { + return object; + } + var args = arrayCopy(arguments); + args.push(assignDefaults); + return assign.apply(undefined, args); + } + + /** + * This method is like `_.findIndex` except that it returns the key of the + * first element `predicate` returns truthy for, instead of the element itself. + * + * If a property name is provided for `predicate` the created "_.property" + * style callback returns the property value of the given element. + * + * If an object is provided for `predicate` the created "_.matches" style + * callback returns `true` for elements that have the properties of the given + * object, else `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to search. + * @param {Function|Object|string} [predicate=_.identity] The function invoked + * per iteration. If a property name or object is provided it is used to + * create a "_.property" or "_.matches" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {string|undefined} Returns the key of the matched element, else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findKey(users, function(chr) { return chr.age < 40; }); + * // => 'barney' (iteration order is not guaranteed) + * + * // using the "_.matches" callback shorthand + * _.findKey(users, { 'age': 1 }); + * // => 'pebbles' + * + * // using the "_.property" callback shorthand + * _.findKey(users, 'active'); + * // => 'barney' + */ + function findKey(object, predicate, thisArg) { + predicate = getCallback(predicate, thisArg, 3); + return baseFind(object, predicate, baseForOwn, true); + } + + /** + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. + * + * If a property name is provided for `predicate` the created "_.property" + * style callback returns the property value of the given element. + * + * If an object is provided for `predicate` the created "_.matches" style + * callback returns `true` for elements that have the properties of the given + * object, else `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to search. + * @param {Function|Object|string} [predicate=_.identity] The function invoked + * per iteration. If a property name or object is provided it is used to + * create a "_.property" or "_.matches" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {string|undefined} Returns the key of the matched element, else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findLastKey(users, function(chr) { return chr.age < 40; }); + * // => returns `pebbles` assuming `_.findKey` returns `barney` + * + * // using the "_.matches" callback shorthand + * _.findLastKey(users, { 'age': 36 }); + * // => 'barney' + * + * // using the "_.property" callback shorthand + * _.findLastKey(users, 'active'); + * // => 'pebbles' + */ + function findLastKey(object, predicate, thisArg) { + predicate = getCallback(predicate, thisArg, 3); + return baseFind(object, predicate, baseForOwnRight, true); + } + + /** + * Iterates over own and inherited enumerable properties of an object invoking + * `iteratee` for each property. The `iteratee` is bound to `thisArg` and invoked + * with three arguments; (value, key, object). Iterator functions may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Object} Returns `object`. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forIn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => logs 'a', 'b', and 'c' (iteration order is not guaranteed) + */ + function forIn(object, iteratee, thisArg) { + if (typeof iteratee != 'function' || typeof thisArg != 'undefined') { + iteratee = bindCallback(iteratee, thisArg, 3); + } + return baseFor(object, iteratee, keysIn); + } + + /** + * This method is like `_.forIn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Object} Returns `object`. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forInRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => logs 'c', 'b', and 'a' assuming `_.forIn ` logs 'a', 'b', and 'c' + */ + function forInRight(object, iteratee, thisArg) { + iteratee = bindCallback(iteratee, thisArg, 3); + return baseForRight(object, iteratee, keysIn); + } + + /** + * Iterates over own enumerable properties of an object invoking `iteratee` + * for each property. The `iteratee` is bound to `thisArg` and invoked with + * three arguments; (value, key, object). Iterator functions may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(n, key) { + * console.log(key); + * }); + * // => logs '0', '1', and 'length' (iteration order is not guaranteed) + */ + function forOwn(object, iteratee, thisArg) { + if (typeof iteratee != 'function' || typeof thisArg != 'undefined') { + iteratee = bindCallback(iteratee, thisArg, 3); + } + return baseForOwn(object, iteratee); + } + + /** + * This method is like `_.forOwn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(n, key) { + * console.log(key); + * }); + * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length' + */ + function forOwnRight(object, iteratee, thisArg) { + iteratee = bindCallback(iteratee, thisArg, 3); + return baseForRight(object, iteratee, keys); + } + + /** + * Creates an array of function property names from all enumerable properties, + * own and inherited, of `object`. + * + * @static + * @memberOf _ + * @alias methods + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the new array of property names. + * @example + * + * _.functions(_); + * // => ['all', 'any', 'bind', ...] + */ + function functions(object) { + return baseFunctions(object, keysIn(object)); + } + + /** + * Checks if `key` exists as a direct property of `object` instead of an + * inherited property. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @param {string} key The key to check. + * @returns {boolean} Returns `true` if `key` is a direct property, else `false`. + * @example + * + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); + * // => true + */ + function has(object, key) { + return object ? hasOwnProperty.call(object, key) : false; + } + + /** + * Creates an object composed of the inverted keys and values of `object`. + * If `object` contains duplicate values, subsequent values overwrite property + * assignments of previous values unless `multiValue` is `true`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to invert. + * @param {boolean} [multiValue] Allow multiple values per key. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. + * @returns {Object} Returns the new inverted object. + * @example + * + * _.invert({ 'first': 'fred', 'second': 'barney' }); + * // => { 'fred': 'first', 'barney': 'second' } + * + * // without `multiValue` + * _.invert({ 'first': 'fred', 'second': 'barney', 'third': 'fred' }); + * // => { 'fred': 'third', 'barney': 'second' } + * + * // with `multiValue` + * _.invert({ 'first': 'fred', 'second': 'barney', 'third': 'fred' }, true); + * // => { 'fred': ['first', 'third'], 'barney': ['second'] } + */ + function invert(object, multiValue, guard) { + if (guard && isIterateeCall(object, multiValue, guard)) { + multiValue = null; + } + var index = -1, + props = keys(object), + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index], + value = object[key]; + + if (multiValue) { + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } + } + else { + result[value] = key; + } + } + return result; + } + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.keys) + * for more details. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + var keys = !nativeKeys ? shimKeys : function(object) { + if (object) { + var Ctor = object.constructor, + length = object.length; + } + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object != 'function' && (length && isLength(length)))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; + }; + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ + function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + length = (length && isLength(length) && + (isArray(object) || (support.nonEnumArgs && isArguments(object))) && length) || 0; + + var Ctor = object.constructor, + index = -1, + isProto = typeof Ctor == 'function' && Ctor.prototype == object, + result = Array(length), + skipIndexes = length > 0; + + while (++index < length) { + result[index] = (index + ''); + } + for (var key in object) { + if (!(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; + } + + /** + * Creates an object with the same keys as `object` and values generated by + * running each own enumerable property of `object` through `iteratee`. The + * iteratee function is bound to `thisArg` and invoked with three arguments; + * (value, key, object). + * + * If a property name is provided for `iteratee` the created "_.property" + * style callback returns the property value of the given element. + * + * If an object is provided for `iteratee` the created "_.matches" style + * callback returns `true` for elements that have the properties of the given + * object, else `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function|Object|string} [iteratee=_.identity] The function invoked + * per iteration. If a property name or object is provided it is used to + * create a "_.property" or "_.matches" style callback respectively. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Object} Returns the new mapped object. + * @example + * + * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(n) { return n * 3; }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + * + * var users = { + * 'fred': { 'user': 'fred', 'age': 40 }, + * 'pebbles': { 'user': 'pebbles', 'age': 1 } + * }; + * + * // using the "_.property" callback shorthand + * _.mapValues(users, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + */ + function mapValues(object, iteratee, thisArg) { + var result = {}; + iteratee = getCallback(iteratee, thisArg, 3); + + baseForOwn(object, function(value, key, object) { + result[key] = iteratee(value, key, object); + }); + return result; + } + + /** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined` into the destination object. Subsequent sources + * overwrite property assignments of previous sources. If `customizer` is + * provided it is invoked to produce the merged values of the destination and + * source properties. If `customizer` returns `undefined` merging is handled + * by the method instead. The `customizer` is bound to `thisArg` and invoked + * with five arguments; (objectValue, sourceValue, key, object, source). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @param {Function} [customizer] The function to customize merging properties. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {Object} Returns `object`. + * @example + * + * var users = { + * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] + * }; + * + * var ages = { + * 'data': [{ 'age': 36 }, { 'age': 40 }] + * }; + * + * _.merge(users, ages); + * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } + * + * // using a customizer callback + * var object = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var other = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(object, other, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] } + */ + var merge = createAssigner(baseMerge); + + /** + * The opposite of `_.pick`; this method creates an object composed of the + * own and inherited enumerable properties of `object` that are not omitted. + * Property names may be specified as individual arguments or as arrays of + * property names. If `predicate` is provided it is invoked for each property + * of `object` omitting the properties `predicate` returns truthy for. The + * predicate is bound to `thisArg` and invoked with three arguments; + * (value, key, object). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {Function|...(string|string[])} [predicate] The function invoked per + * iteration or property names to omit, specified as individual property + * names or arrays of property names. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'user': 'fred', 'age': 40 }; + * + * _.omit(object, 'age'); + * // => { 'user': 'fred' } + * + * _.omit(object, _.isNumber); + * // => { 'user': 'fred' } + */ + function omit(object, predicate, thisArg) { + if (object == null) { + return {}; + } + if (typeof predicate != 'function') { + var props = arrayMap(baseFlatten(arguments, false, false, 1), String); + return pickByArray(object, baseDifference(keysIn(object), props)); + } + predicate = bindCallback(predicate, thisArg, 3); + return pickByCallback(object, function(value, key, object) { + return !predicate(value, key, object); + }); + } + + /** + * Creates a two dimensional array of the key-value pairs for `object`, + * e.g. `[[key1, value1], [key2, value2]]`. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the new array of key-value pairs. + * @example + * + * _.pairs({ 'barney': 36, 'fred': 40 }); + * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed) + */ + function pairs(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + var key = props[index]; + result[index] = [key, object[key]]; + } + return result; + } + + /** + * Creates an object composed of the picked `object` properties. Property + * names may be specified as individual arguments or as arrays of property + * names. If `predicate` is provided it is invoked for each property of `object` + * picking the properties `predicate` returns truthy for. The predicate is + * bound to `thisArg` and invoked with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {Function|...(string|string[])} [predicate] The function invoked per + * iteration or property names to pick, specified as individual property + * names or arrays of property names. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'user': 'fred', 'age': 40 }; + * + * _.pick(object, 'user'); + * // => { 'user': 'fred' } + * + * _.pick(object, _.isString); + * // => { 'user': 'fred' } + */ + function pick(object, predicate, thisArg) { + if (object == null) { + return {}; + } + return typeof predicate == 'function' + ? pickByCallback(object, bindCallback(predicate, thisArg, 3)) + : pickByArray(object, baseFlatten(arguments, false, false, 1)); + } + + /** + * Resolves the value of property `key` on `object`. If the value of `key` is + * a function it is invoked with the `this` binding of `object` and its result + * is returned, else the property value is returned. If the property value is + * `undefined` the `defaultValue` is used in its place. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {string} key The key of the property to resolve. + * @param {*} [defaultValue] The value returned if the property value + * resolves to `undefined`. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'user': 'fred', 'age': _.constant(40) }; + * + * _.result(object, 'user'); + * // => 'fred' + * + * _.result(object, 'age'); + * // => 40 + * + * _.result(object, 'status', 'busy'); + * // => 'busy' + * + * _.result(object, 'status', _.constant('busy')); + * // => 'busy' + */ + function result(object, key, defaultValue) { + var value = object == null ? undefined : object[key]; + if (typeof value == 'undefined') { + value = defaultValue; + } + return isFunction(value) ? value.call(object) : value; + } + + /** + * An alternative to `_.reduce`; this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own enumerable + * properties through `iteratee`, with each invocation potentially mutating + * the `accumulator` object. The `iteratee` is bound to `thisArg` and invoked + * with four arguments; (accumulator, value, key, object). Iterator functions + * may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Object + * @param {Array|Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {*} Returns the accumulated value. + * @example + * + * var squares = _.transform([1, 2, 3, 4, 5, 6], function(result, n) { + * n *= n; + * if (n % 2) { + * return result.push(n) < 3; + * } + * }); + * // => [1, 9, 25] + * + * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, n, key) { + * result[key] = n * 3; + * }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function transform(object, iteratee, accumulator, thisArg) { + var isArr = isArray(object) || isTypedArray(object); + iteratee = getCallback(iteratee, thisArg, 4); + + if (accumulator == null) { + if (isArr || isObject(object)) { + var Ctor = object.constructor; + if (isArr) { + accumulator = isArray(object) ? new Ctor : []; + } else { + accumulator = baseCreate(typeof Ctor == 'function' && Ctor.prototype); + } + } else { + accumulator = {}; + } + } + (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) { + return iteratee(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Creates an array of the own enumerable property values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.values(new Foo); + * // => [1, 2] (iteration order is not guaranteed) + * + * _.values('hi'); + * // => ['h', 'i'] + */ + function values(object) { + return baseValues(object, keys(object)); + } + + /** + * Creates an array of the own and inherited enumerable property values + * of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.valuesIn(new Foo); + * // => [1, 2, 3] (iteration order is not guaranteed) + */ + function valuesIn(object) { + return baseValues(object, keysIn(object)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is provided a number between `0` and the given number is returned. + * If `floating` is `true`, or either `min` or `max` are floats, a floating-point + * number is returned instead of an integer. + * + * @static + * @memberOf _ + * @category Number + * @param {number} [min=0] The minimum possible value. + * @param {number} [max=1] The maximum possible value. + * @param {boolean} [floating] Specify returning a floating-point number. + * @returns {number} Returns the random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ + function random(min, max, floating) { + if (floating && isIterateeCall(min, max, floating)) { + max = floating = null; + } + var noMin = min == null, + noMax = max == null; + + if (floating == null) { + if (noMax && typeof min == 'boolean') { + floating = min; + min = 1; + } + else if (typeof max == 'boolean') { + floating = max; + noMax = true; + } + } + if (noMin && noMax) { + max = 1; + noMax = false; + } + min = +min || 0; + if (noMax) { + max = min; + min = 0; + } else { + max = +max || 0; + } + if (floating || min % 1 || max % 1) { + var rand = nativeRandom(); + return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max); + } + return baseRandom(min, max); + } + + /*------------------------------------------------------------------------*/ + + /** + * Converts `string` to camel case. + * See [Wikipedia](https://en.wikipedia.org/wiki/CamelCase) for more details. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Foo Bar'); + * // => 'fooBar' + * + * _.camelCase('--foo-bar'); + * // => 'fooBar' + * + * _.camelCase('__foo_bar__'); + * // => 'fooBar' + */ + var camelCase = createCompounder(function(result, word, index) { + word = word.toLowerCase(); + return result + (index ? (word.charAt(0).toUpperCase() + word.slice(1)) : word); + }); + + /** + * Capitalizes the first character of `string`. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * _.capitalize('fred'); + * // => 'Fred' + */ + function capitalize(string) { + string = baseToString(string); + return string && (string.charAt(0).toUpperCase() + string.slice(1)); + } + + /** + * Deburrs `string` by converting latin-1 supplementary letters to basic latin letters. + * See [Wikipedia](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * for more details. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to deburr. + * @returns {string} Returns the deburred string. + * @example + * + * _.deburr('déjà vu'); + * // => 'deja vu' + */ + function deburr(string) { + string = baseToString(string); + return string && string.replace(reLatin1, deburrLetter); + } + + /** + * Checks if `string` ends with the given target string. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to search. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search from. + * @returns {boolean} Returns `true` if `string` ends with `target`, else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = baseToString(string); + target = (target + ''); + + var length = string.length; + position = (typeof position == 'undefined' ? length : nativeMin(position < 0 ? 0 : (+position || 0), length)) - target.length; + return position >= 0 && string.indexOf(target, position) == position; + } + + /** + * Converts the characters "&", "<", ">", '"', "'", and '`', in `string` to + * their corresponding HTML entities. + * + * **Note:** No other characters are escaped. To escape additional characters + * use a third-party library like [_he_](https://mths.be/he). + * + * Though the ">" character is escaped for symmetry, characters like + * ">" and "/" don't require escaping in HTML and have no special meaning + * unless they're part of a tag or unquoted attribute value. + * See [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) + * (under "semi-related fun fact") for more details. + * + * Backticks are escaped because in Internet Explorer < 9, they can break out + * of attribute values or HTML comments. See [#102](https://html5sec.org/#102), + * [#108](https://html5sec.org/#108), and [#133](https://html5sec.org/#133) of + * the [HTML5 Security Cheatsheet](https://html5sec.org/) for more details. + * + * When working with HTML you should always quote attribute values to reduce + * XSS vectors. See [Ryan Grove's article](http://wonko.com/post/html-escaping) + * for more details. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles' + */ + function escape(string) { + // Reset `lastIndex` because in IE < 9 `String#replace` does not. + string = baseToString(string); + return (string && reHasUnescapedHtml.test(string)) + ? string.replace(reUnescapedHtml, escapeHtmlChar) + : string; + } + + /** + * Escapes the `RegExp` special characters "\", "^", "$", ".", "|", "?", "*", + * "+", "(", ")", "[", "]", "{" and "}" in `string`. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](https://lodash.com/)'); + * // => '\[lodash\]\(https://lodash\.com/\)' + */ + function escapeRegExp(string) { + string = baseToString(string); + return (string && reHasRegExpChars.test(string)) + ? string.replace(reRegExpChars, '\\$&') + : string; + } + + /** + * Converts `string` to kebab case (a.k.a. spinal case). + * See [Wikipedia](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles) for + * more details. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Foo Bar'); + * // => 'foo-bar' + * + * _.kebabCase('fooBar'); + * // => 'foo-bar' + * + * _.kebabCase('__foo_bar__'); + * // => 'foo-bar' + */ + var kebabCase = createCompounder(function(result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Pads `string` on the left and right sides if it is shorter then the given + * padding length. The `chars` string may be truncated if the number of padding + * characters can't be evenly divided by the padding length. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = baseToString(string); + length = +length; + + var strLength = string.length; + if (strLength >= length || !nativeIsFinite(length)) { + return string; + } + var mid = (length - strLength) / 2, + leftLength = floor(mid), + rightLength = ceil(mid); + + chars = createPad('', rightLength, chars); + return chars.slice(0, leftLength) + string + chars; + } + + /** + * Pads `string` on the left side if it is shorter then the given padding + * length. The `chars` string may be truncated if the number of padding + * characters exceeds the padding length. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padLeft('abc', 6); + * // => ' abc' + * + * _.padLeft('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padLeft('abc', 3); + * // => 'abc' + */ + function padLeft(string, length, chars) { + string = baseToString(string); + return string && (createPad(string, length, chars) + string); + } + + /** + * Pads `string` on the right side if it is shorter then the given padding + * length. The `chars` string may be truncated if the number of padding + * characters exceeds the padding length. + * + * @static + * @memberOf _ + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padRight('abc', 6); + * // => 'abc ' + * + * _.padRight('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padRight('abc', 3); + * // => 'abc' + */ + function padRight(string, length, chars) { + string = baseToString(string); + return string && (string + createPad(string, length, chars)); + } + + /** + * Converts `string` to an integer of the specified radix. If `radix` is + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal, + * in which case a `radix` of `16` is used. + * + * **Note:** This method aligns with the ES5 implementation of `parseInt`. + * See the [ES5 spec](https://es5.github.io/#E) for more details. + * + * @static + * @memberOf _ + * @category String + * @param {string} string The string to convert. + * @param {number} [radix] The radix to interpret `value` by. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. + * @returns {number} Returns the converted integer. + * @example + * + * _.parseInt('08'); + * // => 8 + * + * _.map(['6', '08', '10'], _.parseInt); + * // => [6, 8, 10] + */ + function parseInt(string, radix, guard) { + if (guard && isIterateeCall(string, radix, guard)) { + radix = 0; + } + return nativeParseInt(string, radix); + } + // Fallback for environments with pre-ES5 implementations. + if (nativeParseInt(whitespace + '08') != 8) { + parseInt = function(string, radix, guard) { + // Firefox < 21 and Opera < 15 follow ES3 for `parseInt`. + // Chrome fails to trim leading} and {@code } tags in your source with
+ * {@code class=prettyprint.}
+ * You can also use the (html deprecated) {@code } tag, but the pretty
+ * printer needs to do more substantial DOM manipulations to support that, so
+ * some css styles may not be preserved.
+ *
+ * That's it. I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the {@code } or {@code } element to specify the
+ * language, as in {@code }. Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ *
+ * Change log:
+ * cbeust, 2006/08/22
+ *
+ * Java annotations (start with "@") are now captured as literals ("lit")
+ *
+ * @requires console
+ */
+
+// JSLint declarations
+/*global console, document, navigator, setTimeout, window, define */
+
+/** @define {boolean} */
+var IN_GLOBAL_SCOPE = true;
+
+/**
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
+ * UI events.
+ * If set to {@code false}, {@code prettyPrint()} is synchronous.
+ */
+window['PR_SHOULD_USE_CONTINUATION'] = true;
+
+/**
+ * Pretty print a chunk of code.
+ * @param {string} sourceCodeHtml The HTML to pretty print.
+ * @param {string} opt_langExtension The language name to use.
+ * Typically, a filename extension like 'cpp' or 'java'.
+ * @param {number|boolean} opt_numberLines True to number lines,
+ * or the 1-indexed number of the first line in sourceCodeHtml.
+ * @return {string} code as html, but prettier
+ */
+var prettyPrintOne;
+/**
+ * Find all the {@code } and {@code } tags in the DOM with
+ * {@code class=prettyprint} and prettify them.
+ *
+ * @param {Function} opt_whenDone called when prettifying is done.
+ * @param {HTMLElement|HTMLDocument} opt_root an element or document
+ * containing all the elements to pretty print.
+ * Defaults to {@code document.body}.
+ */
+var prettyPrint;
+
+
+(function () {
+ var win = window;
+ // Keyword lists for various languages.
+ // We use things that coerce to strings to make them compact when minified
+ // and to defeat aggressive optimizers that fold large string constants.
+ var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
+ var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," +
+ "double,enum,extern,float,goto,inline,int,long,register,short,signed," +
+ "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
+ var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
+ "new,operator,private,protected,public,this,throw,true,try,typeof"];
+ var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
+ "concept,concept_map,const_cast,constexpr,decltype,delegate," +
+ "dynamic_cast,explicit,export,friend,generic,late_check," +
+ "mutable,namespace,nullptr,property,reinterpret_cast,static_assert," +
+ "static_cast,template,typeid,typename,using,virtual,where"];
+ var JAVA_KEYWORDS = [COMMON_KEYWORDS,
+ "abstract,assert,boolean,byte,extends,final,finally,implements,import," +
+ "instanceof,interface,null,native,package,strictfp,super,synchronized," +
+ "throws,transient"];
+ var CSHARP_KEYWORDS = [JAVA_KEYWORDS,
+ "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
+ "fixed,foreach,from,group,implicit,in,internal,into,is,let," +
+ "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," +
+ "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," +
+ "var,virtual,where"];
+ var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
+ "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
+ "throw,true,try,unless,until,when,while,yes";
+ var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
+ "debugger,eval,export,function,get,null,set,undefined,var,with," +
+ "Infinity,NaN"];
+ var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
+ "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
+ "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
+ var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
+ "elif,except,exec,finally,from,global,import,in,is,lambda," +
+ "nonlocal,not,or,pass,print,raise,try,with,yield," +
+ "False,True,None"];
+ var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
+ "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
+ "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
+ "BEGIN,END"];
+ var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "as,assert,const,copy,drop," +
+ "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv," +
+ "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"];
+ var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
+ "function,in,local,set,then,until"];
+ var ALL_KEYWORDS = [
+ CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,
+ PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
+ var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
+
+ // token style names. correspond to css classes
+ /**
+ * token style for a string literal
+ * @const
+ */
+ var PR_STRING = 'str';
+ /**
+ * token style for a keyword
+ * @const
+ */
+ var PR_KEYWORD = 'kwd';
+ /**
+ * token style for a comment
+ * @const
+ */
+ var PR_COMMENT = 'com';
+ /**
+ * token style for a type
+ * @const
+ */
+ var PR_TYPE = 'typ';
+ /**
+ * token style for a literal value. e.g. 1, null, true.
+ * @const
+ */
+ var PR_LITERAL = 'lit';
+ /**
+ * token style for a punctuation string.
+ * @const
+ */
+ var PR_PUNCTUATION = 'pun';
+ /**
+ * token style for plain text.
+ * @const
+ */
+ var PR_PLAIN = 'pln';
+
+ /**
+ * token style for an sgml tag.
+ * @const
+ */
+ var PR_TAG = 'tag';
+ /**
+ * token style for a markup declaration such as a DOCTYPE.
+ * @const
+ */
+ var PR_DECLARATION = 'dec';
+ /**
+ * token style for embedded source.
+ * @const
+ */
+ var PR_SOURCE = 'src';
+ /**
+ * token style for an sgml attribute name.
+ * @const
+ */
+ var PR_ATTRIB_NAME = 'atn';
+ /**
+ * token style for an sgml attribute value.
+ * @const
+ */
+ var PR_ATTRIB_VALUE = 'atv';
+
+ /**
+ * A class that indicates a section of markup that is not code, e.g. to allow
+ * embedding of line numbers within code listings.
+ * @const
+ */
+ var PR_NOCODE = 'nocode';
+
+
+
+ /**
+ * A set of tokens that can precede a regular expression literal in
+ * javascript
+ * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
+ * has the full list, but I've removed ones that might be problematic when
+ * seen in languages that don't support regular expression literals.
+ *
+ * Specifically, I've removed any keywords that can't precede a regexp
+ * literal in a syntactically legal javascript program, and I've removed the
+ * "in" keyword since it's not a keyword in many languages, and might be used
+ * as a count of inches.
+ *
+ *
The link above does not accurately describe EcmaScript rules since
+ * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+ * very well in practice.
+ *
+ * @private
+ * @const
+ */
+ var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
+
+ // CAVEAT: this does not properly handle the case where a regular
+ // expression immediately follows another since a regular expression may
+ // have flags for case-sensitivity and the like. Having regexp tokens
+ // adjacent is not valid in any language I'm aware of, so I'm punting.
+ // TODO: maybe style special characters inside a regexp as punctuation.
+
+ /**
+ * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
+ * matches the union of the sets of strings matched by the input RegExp.
+ * Since it matches globally, if the input strings have a start-of-input
+ * anchor (/^.../), it is ignored for the purposes of unioning.
+ * @param {Array.} regexs non multiline, non-global regexs.
+ * @return {RegExp} a global regex.
+ */
+ function combinePrefixPatterns(regexs) {
+ var capturedGroupIndex = 0;
+
+ var needToFoldCase = false;
+ var ignoreCase = false;
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.ignoreCase) {
+ ignoreCase = true;
+ } else if (/[a-z]/i.test(regex.source.replace(
+ /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
+ needToFoldCase = true;
+ ignoreCase = false;
+ break;
+ }
+ }
+
+ var escapeCharToCodeUnit = {
+ 'b': 8,
+ 't': 9,
+ 'n': 0xa,
+ 'v': 0xb,
+ 'f': 0xc,
+ 'r': 0xd
+ };
+
+ function decodeEscape(charsetPart) {
+ var cc0 = charsetPart.charCodeAt(0);
+ if (cc0 !== 92 /* \\ */) {
+ return cc0;
+ }
+ var c1 = charsetPart.charAt(1);
+ cc0 = escapeCharToCodeUnit[c1];
+ if (cc0) {
+ return cc0;
+ } else if ('0' <= c1 && c1 <= '7') {
+ return parseInt(charsetPart.substring(1), 8);
+ } else if (c1 === 'u' || c1 === 'x') {
+ return parseInt(charsetPart.substring(2), 16);
+ } else {
+ return charsetPart.charCodeAt(1);
+ }
+ }
+
+ function encodeEscape(charCode) {
+ if (charCode < 0x20) {
+ return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
+ }
+ var ch = String.fromCharCode(charCode);
+ return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
+ ? "\\" + ch : ch;
+ }
+
+ function caseFoldCharset(charSet) {
+ var charsetParts = charSet.substring(1, charSet.length - 1).match(
+ new RegExp(
+ '\\\\u[0-9A-Fa-f]{4}'
+ + '|\\\\x[0-9A-Fa-f]{2}'
+ + '|\\\\[0-3][0-7]{0,2}'
+ + '|\\\\[0-7]{1,2}'
+ + '|\\\\[\\s\\S]'
+ + '|-'
+ + '|[^-\\\\]',
+ 'g'));
+ var ranges = [];
+ var inverse = charsetParts[0] === '^';
+
+ var out = ['['];
+ if (inverse) { out.push('^'); }
+
+ for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
+ var p = charsetParts[i];
+ if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups.
+ out.push(p);
+ } else {
+ var start = decodeEscape(p);
+ var end;
+ if (i + 2 < n && '-' === charsetParts[i + 1]) {
+ end = decodeEscape(charsetParts[i + 2]);
+ i += 2;
+ } else {
+ end = start;
+ }
+ ranges.push([start, end]);
+ // If the range might intersect letters, then expand it.
+ // This case handling is too simplistic.
+ // It does not deal with non-latin case folding.
+ // It works for latin source code identifiers though.
+ if (!(end < 65 || start > 122)) {
+ if (!(end < 65 || start > 90)) {
+ ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
+ }
+ if (!(end < 97 || start > 122)) {
+ ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
+ }
+ }
+ }
+ }
+
+ // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+ // -> [[1, 12], [14, 14], [16, 17]]
+ ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); });
+ var consolidatedRanges = [];
+ var lastRange = [];
+ for (var i = 0; i < ranges.length; ++i) {
+ var range = ranges[i];
+ if (range[0] <= lastRange[1] + 1) {
+ lastRange[1] = Math.max(lastRange[1], range[1]);
+ } else {
+ consolidatedRanges.push(lastRange = range);
+ }
+ }
+
+ for (var i = 0; i < consolidatedRanges.length; ++i) {
+ var range = consolidatedRanges[i];
+ out.push(encodeEscape(range[0]));
+ if (range[1] > range[0]) {
+ if (range[1] + 1 > range[0]) { out.push('-'); }
+ out.push(encodeEscape(range[1]));
+ }
+ }
+ out.push(']');
+ return out.join('');
+ }
+
+ function allowAnywhereFoldCaseAndRenumberGroups(regex) {
+ // Split into character sets, escape sequences, punctuation strings
+ // like ('(', '(?:', ')', '^'), and runs of characters that do not
+ // include any of the above.
+ var parts = regex.source.match(
+ new RegExp(
+ '(?:'
+ + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set
+ + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape
+ + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape
+ + '|\\\\[0-9]+' // a back-reference or octal escape
+ + '|\\\\[^ux0-9]' // other escape sequence
+ + '|\\(\\?[:!=]' // start of a non-capturing group
+ + '|[\\(\\)\\^]' // start/end of a group, or line start
+ + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters
+ + ')',
+ 'g'));
+ var n = parts.length;
+
+ // Maps captured group numbers to the number they will occupy in
+ // the output or to -1 if that has not been determined, or to
+ // undefined if they need not be capturing in the output.
+ var capturedGroups = [];
+
+ // Walk over and identify back references to build the capturedGroups
+ // mapping.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ // groups are 1-indexed, so max group index is count of '('
+ ++groupIndex;
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue) {
+ if (decimalValue <= groupIndex) {
+ capturedGroups[decimalValue] = -1;
+ } else {
+ // Replace with an unambiguous escape sequence so that
+ // an octal escape sequence does not turn into a backreference
+ // to a capturing group from an earlier regex.
+ parts[i] = encodeEscape(decimalValue);
+ }
+ }
+ }
+ }
+
+ // Renumber groups and reduce capturing groups to non-capturing groups
+ // where possible.
+ for (var i = 1; i < capturedGroups.length; ++i) {
+ if (-1 === capturedGroups[i]) {
+ capturedGroups[i] = ++capturedGroupIndex;
+ }
+ }
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ ++groupIndex;
+ if (!capturedGroups[groupIndex]) {
+ parts[i] = '(?:';
+ }
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ parts[i] = '\\' + capturedGroups[decimalValue];
+ }
+ }
+ }
+
+ // Remove any prefix anchors so that the output will match anywhere.
+ // ^^ really does mean an anchored match though.
+ for (var i = 0; i < n; ++i) {
+ if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
+ }
+
+ // Expand letters to groups to handle mixing of case-sensitive and
+ // case-insensitive patterns if necessary.
+ if (regex.ignoreCase && needToFoldCase) {
+ for (var i = 0; i < n; ++i) {
+ var p = parts[i];
+ var ch0 = p.charAt(0);
+ if (p.length >= 2 && ch0 === '[') {
+ parts[i] = caseFoldCharset(p);
+ } else if (ch0 !== '\\') {
+ // TODO: handle letters in numeric escapes.
+ parts[i] = p.replace(
+ /[a-zA-Z]/g,
+ function (ch) {
+ var cc = ch.charCodeAt(0);
+ return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
+ });
+ }
+ }
+ }
+
+ return parts.join('');
+ }
+
+ var rewritten = [];
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.global || regex.multiline) { throw new Error('' + regex); }
+ rewritten.push(
+ '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
+ }
+
+ return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
+ }
+
+ /**
+ * Split markup into a string of source code and an array mapping ranges in
+ * that string to the text nodes in which they appear.
+ *
+ *
+ * The HTML DOM structure:
+ *
+ * (Element "p"
+ * (Element "b"
+ * (Text "print ")) ; #1
+ * (Text "'Hello '") ; #2
+ * (Element "br") ; #3
+ * (Text " + 'World';")) ; #4
+ *
+ *
+ * corresponds to the HTML
+ * {@code
print 'Hello '
+ 'World';
}.
+ *
+ *
+ * It will produce the output:
+ *
+ * {
+ * sourceCode: "print 'Hello '\n + 'World';",
+ * // 1 2
+ * // 012345678901234 5678901234567
+ * spans: [0, #1, 6, #2, 14, #3, 15, #4]
+ * }
+ *
+ *
+ * where #1 is a reference to the {@code "print "} text node above, and so
+ * on for the other text nodes.
+ *
+ *
+ *
+ * The {@code} spans array is an array of pairs. Even elements are the start
+ * indices of substrings, and odd elements are the text nodes (or BR elements)
+ * that contain the text for those substrings.
+ * Substrings continue until the next index or the end of the source.
+ *
+ *
+ * @param {Node} node an HTML DOM subtree containing source-code.
+ * @param {boolean} isPreformatted true if white-space in text nodes should
+ * be considered significant.
+ * @return {Object} source code and the text nodes in which they occur.
+ */
+ function extractSourceSpans(node, isPreformatted) {
+ var nocode = /(?:^|\s)nocode(?:\s|$)/;
+
+ var chunks = [];
+ var length = 0;
+ var spans = [];
+ var k = 0;
+
+ function walk(node) {
+ var type = node.nodeType;
+ if (type == 1) { // Element
+ if (nocode.test(node.className)) { return; }
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ walk(child);
+ }
+ var nodeName = node.nodeName.toLowerCase();
+ if ('br' === nodeName || 'li' === nodeName) {
+ chunks[k] = '\n';
+ spans[k << 1] = length++;
+ spans[(k++ << 1) | 1] = node;
+ }
+ } else if (type == 3 || type == 4) { // Text
+ var text = node.nodeValue;
+ if (text.length) {
+ if (!isPreformatted) {
+ text = text.replace(/[ \t\r\n]+/g, ' ');
+ } else {
+ text = text.replace(/\r\n?/g, '\n'); // Normalize newlines.
+ }
+ // TODO: handle tabs here?
+ chunks[k] = text;
+ spans[k << 1] = length;
+ length += text.length;
+ spans[(k++ << 1) | 1] = node;
+ }
+ }
+ }
+
+ walk(node);
+
+ return {
+ sourceCode: chunks.join('').replace(/\n$/, ''),
+ spans: spans
+ };
+ }
+
+ /**
+ * Apply the given language handler to sourceCode and add the resulting
+ * decorations to out.
+ * @param {number} basePos the index of sourceCode within the chunk of source
+ * whose decorations are already present on out.
+ */
+ function appendDecorations(basePos, sourceCode, langHandler, out) {
+ if (!sourceCode) { return; }
+ var job = {
+ sourceCode: sourceCode,
+ basePos: basePos
+ };
+ langHandler(job);
+ out.push.apply(out, job.decorations);
+ }
+
+ var notWs = /\S/;
+
+ /**
+ * Given an element, if it contains only one child element and any text nodes
+ * it contains contain only space characters, return the sole child element.
+ * Otherwise returns undefined.
+ *
+ * This is meant to return the CODE element in {@code
} when
+ * there is a single child element that contains all the non-space textual
+ * content, but not to return anything where there are multiple child elements
+ * as in {@code ...
...
} or when there
+ * is textual content.
+ */
+ function childContentWrapper(element) {
+ var wrapper = undefined;
+ for (var c = element.firstChild; c; c = c.nextSibling) {
+ var type = c.nodeType;
+ wrapper = (type === 1) // Element Node
+ ? (wrapper ? element : c)
+ : (type === 3) // Text Node
+ ? (notWs.test(c.nodeValue) ? element : wrapper)
+ : wrapper;
+ }
+ return wrapper === element ? undefined : wrapper;
+ }
+
+ /** Given triples of [style, pattern, context] returns a lexing function,
+ * The lexing function interprets the patterns to find token boundaries and
+ * returns a decoration list of the form
+ * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+ * where index_n is an index into the sourceCode, and style_n is a style
+ * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to
+ * all characters in sourceCode[index_n-1:index_n].
+ *
+ * The stylePatterns is a list whose elements have the form
+ * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+ *
+ * Style is a style constant like PR_PLAIN, or can be a string of the
+ * form 'lang-FOO', where FOO is a language extension describing the
+ * language of the portion of the token in $1 after pattern executes.
+ * E.g., if style is 'lang-lisp', and group 1 contains the text
+ * '(hello (world))', then that portion of the token will be passed to the
+ * registered lisp handler for formatting.
+ * The text before and after group 1 will be restyled using this decorator
+ * so decorators should take care that this doesn't result in infinite
+ * recursion. For example, the HTML lexer rule for SCRIPT elements looks
+ * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match
+ * '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/js/angular-bootstrap-prettify.js b/docs/js/angular-bootstrap-prettify.js
new file mode 100644
index 000000000..e14861056
--- /dev/null
+++ b/docs/js/angular-bootstrap-prettify.js
@@ -0,0 +1,320 @@
+'use strict';
+
+var directive = {};
+var service = { value: {} };
+
+var DEPENDENCIES = {
+ 'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
+ 'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
+ 'angular-route.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-route.min.js',
+ 'angular-animate.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-animate.min.js',
+ 'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
+ 'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
+};
+
+
+function escape(text) {
+ return text.
+ replace(/\&/g, '&').
+ replace(/\/g, '>').
+ replace(/"/g, '"');
+}
+
+/**
+ * http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
+ * http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
+ */
+function setHtmlIe8SafeWay(element, html) {
+ var newElement = angular.element('' + html + '
');
+
+ element.html('');
+ element.append(newElement.contents());
+ return element;
+}
+
+
+directive.jsFiddle = function(getEmbeddedTemplate, escape, script) {
+ return {
+ terminal: true,
+ link: function(scope, element, attr) {
+ var name = '',
+ stylesheet = '\n',
+ fields = {
+ html: '',
+ css: '',
+ js: ''
+ };
+
+ angular.forEach(attr.jsFiddle.split(' '), function(file, index) {
+ var fileType = file.split('.')[1];
+
+ if (fileType == 'html') {
+ if (index == 0) {
+ fields[fileType] +=
+ '\n' +
+ getEmbeddedTemplate(file, 2);
+ } else {
+ fields[fileType] += '\n\n\n \n' +
+ ' \n';
+ }
+ } else {
+ fields[fileType] += getEmbeddedTemplate(file) + '\n';
+ }
+ });
+
+ fields.html += '\n';
+
+ setHtmlIe8SafeWay(element,
+ '
' + html + ''); + + element.html(''); + element.append(newElement.contents()); + return element; + } + + return { + compile: function(element, attr) { + var properties = { + head: '', + module: '', + body: element.text() + }, + html = "\n\n \n{{head:4}} \n \n{{body:4}} \n"; + + angular.forEach(loadedUrls.base, function(dep) { + properties.head += '\n'; + }); + + angular.forEach((attr.ngHtmlWrapLoaded || '').split(' '), function(dep) { + if (!dep) return; + var ext = dep.split(/\./).pop(); + + if (ext == 'css') { + properties.head += '\n'; + } else if(ext == 'js' && dep !== 'angular.js') { + properties.head += '\n'; + } else if (dep !== 'angular.js') { + properties.module = '="' + dep + '"'; + } + }); + + setHtmlIe8SafeWay(element, escape(templateMerge(html, properties))); + } + }; +}; + + +docsApp.directive.focused = function($timeout) { + return function(scope, element, attrs) { + element[0].focus(); + element.bind('focus', function() { + scope.$apply(attrs.focused + '=true'); + }); + element.bind('blur', function() { + // have to use $timeout, so that we close the drop-down after the user clicks, + // otherwise when the user clicks we process the closing before we process the click. + $timeout(function() { + scope.$eval(attrs.focused + '=false'); + }); + }); + scope.$eval(attrs.focused + '=true'); + }; +}; + + +docsApp.directive.code = function() { + return { restrict:'E', terminal: true }; +}; + + +docsApp.directive.sourceEdit = function(getEmbeddedTemplate) { + return { + template: '
Grid
+
+Grid defines a logical grid. Any non-dom properties and elements needed by the grid should +be defined in this class
Grid(id);+
id – {string} –
+id to assign to grid
creates GridColumn objects from the columnDefinition. Calls each registered +columnBuilder to further process the column
{Promise}
+– a promise to load any needed column resources
calls each styleComputation function
returns a grid column for the field name
field – {string} –
+field name
creates or removes GridRow objects from the newRawData array. Calls each registered +rowBuilder to further process the row
+ +Rows are identified using the gridOptions.rowEquality function
processes all RowBuilders for the gridRow
{GridRow}
+– the gridRow with all additional behaivor added
When the build creates columns from column definitions, the columnbuilders will be called to add +additional properties to the column.
columnsProcessor – {function(colDef, col, gridOptions)} –
+function to be called
registered a styleComputation function
GridColumn
+
+Wrapper for the GridOptions.colDefs items. Allows for needed properties and functions +to be assigned to a grid column
GridColumn(colDef, index);+
colDef – {ColDef} –
+Column definition
index – {number} –
+the current position of the column in the array
GridOptions
+
+Default GridOptions class. GridOptions are defined by the application developer and overlaid +over this object.
GridOptions(id);+
id – {string} –
+id to assign to grid
By default, rows are compared using object equality. This option can be overridden +to compare on any data item property or function
entityA – {object} –
+First Data Item to compare
entityB – {object} –
+Second Data Item to compare
(optional) Array of columnDef objects. Only required property is field
var columnDefs = [{field:'field1'}, {field:'field2'}];
Array of data to be rendered to grid. Array can contain complex objects
GridRow
+
+Wrapper for the GridOptions.data rows. Allows for needed properties and functions +to be assigned to a grid row
GridRow(entity, index);+
entity – {object} –
+the array item from GridOptions.data
index – {number} –
+the current position of the row in the array
+
+The ui-grid API consists of the core ui.grid documentation, plus documentation for each of the features that +you can choose to add to the grid.
+ +In general the features will extend the core ui-grid configuration. So, for example, if you wanted to configure +the core ui-grid, you might choose to set some options and columns on your grid (the documentation for these is +found in gridOptions and columnDef.
+ ++ $scope.gridOptions = { + enableSorting: true, + columnDefs: [ + { name: 'field1', enableSorting: false }, + { name: 'field2' }, + { name: 'field3', visible: false } + ] + }; ++ +
If you had enabled the edit feature, then the columnDefs and gridOptions would have additional settings, the documentation +for these is at columnDef and gridOptions, but you +still set these through the same mechanism:
+ ++ $scope.gridOptions = { + enableSorting: true, + enableCellEditOnFocus: true, + columnDefs: [ + { name: 'field1', enableSorting: false, enableCellEdit: false }, + { name: 'field2' }, + { name: 'field3', visible: false } + ] + }; ++ +
In general you'll therefore use the documentation for the core ui.grid module, and then the additional documentation +for any feature that you have enabled.
+
+This module provides auto-resizing functionality to ui-grid
ColDef
+(api in module ui.grid.cellNav
+)
+Column Definitions for cellNav feature
ColumnDef
+(api in module ui.grid.cellNav
+)
+Column Definitions for cellNav feature, these are available to be +set using the ui-grid gridOptions.columnDefs
Enable focus on a cell within this column.
+
Defaults to true
GridOptions
+(api in module ui.grid.cellNav
+)
+GridOptions for cellNav feature, these are available to be +set using the ui-grid gridOptions
Enable multiple cell selection only when using the ctrlKey or shiftKey.
+
Defaults to false
GridRow
+(api in module ui.grid.cellNav
+)
+GridRow settings for cellNav feature, these are available to be +set only internally (for example, by other features)
Enable focus on a cell within this row. If set to false then no cells
+in this row can be focused - group header rows as an example would set this to false.
+
Defaults to true
PublicApi
+(api in module ui.grid.cellNav
+)
+Public Api for cellNav feature
returns an array containing the current selection
+
array is empty if no selection has occurred
returns the current (or last if Grid does not have focus) focused row and column
+
value is null if no selection has occurred
returns the index in the order in which the RowCol was selected, returns -1 if the RowCol +isn't selected
rowCol – {object} –
+the rowCol to evaluate
brings the specified row and column into view
rowEntity – {object} –
+gridOptions.data[] array instance to make visible
colDef – {object} –
+to make visible
brings the specified row and column into view, and sets focus +to that cell
rowEntity – {object} –
+gridOptions.data[] array instance to make visible and set focus
colDef – {object} –
+to make visible and set focus
brings the specified row and column fully into view if it isn't already
row – {GridRow} –
+grid row that we should make fully visible
col – {GridCol} –
+grid col to make fully visible
uiGridCellNavConstants
+(constant in module ui.grid.cellNav
+)
+constants available in cellNav
uiGridEditConstants
+(constant in module ui.grid.cellNav
+)
+constants available in cellNav
uiCellNav
+(directive in module ui.grid.cellNav
+)
+Adds cell navigation features to the grid columns
<ui-cell-nav> +</ui-cell-nav>+as attribute
<div ui-cell-nav> + ... +</div>+
uiGridCell
+(directive in module ui.grid.cellNav
+)
+Stacks on top of ui.grid.uiGridCell to provide cell navigation
<div ui-grid-cell> + ... +</div>+
CellNav
+(object in module ui.grid.cellNav
+)
+returns a CellNav prototype function
object:CellNav(rowContainer, colContainer, leftColContainer, rightColContainer);+
rowContainer – {object} –
+container for rows
colContainer – {object} –
+parent column container
leftColContainer – {object} –
+column container to the left of parent
rightColContainer – {object} –
+column container to the right of parent
uiGridCellNavService
+(service in module ui.grid.cellNav
+)
+Services for cell navigation features. If you don't like the key maps we use, +or the direction cells navigation, override with a service decorator (see angular docs)
columnBuilder function that adds cell navigation properties to grid column
{promise}
+– promise that will load any needed templates when resolved
decorates grid renderContainers with cellNav functions
determines which direction to for a given keyDown event
{uiGridCellNavConstants.direction}
+– direction
Get the current drawn width of the columns in the +grid up to the numbered column, and add an apportionment for the +column that we're on. So if we are on column 0, we want to scroll +0% (i.e. exclude this column from calc). If we're on the last column +we want to scroll to 100% (i.e. include this column in the calc). So +we include (thisColIndex / totalNumberCols) % of this column width
grid – {Grid} –
+the grid you'd like to act upon, usually available +from gridApi.grid
upToCol – {gridCol} –
+the column to total up to and including
Scroll the grid such that the specified +row and column is in view
grid – {Grid} –
+the grid you'd like to act upon, usually available +from gridApi.grid
rowEntity – {object} –
+gridOptions.data[] array instance to make visible
colDef – {object} –
+to make visible
Scroll the grid such that the specified +row and column is in view, and set focus to the cell in that row and column
grid – {Grid} –
+the grid you'd like to act upon, usually available +from gridApi.grid
rowEntity – {object} –
+gridOptions.data[] array instance to make visible and set focus to
colDef – {object} –
+to make visible and set focus to
Scrolls the grid to make a certain row and column combo visible, +in the case that it is not completely visible on the screen already.
grid – {Grid} –
+the grid you'd like to act upon, usually available +from gridApi.grid
gridRow – {GridRow} –
+row to make visible
gridCol – {GridCol} –
+column to make visible
Like scrollTo, but takes gridRow and gridCol. +In calculating the scroll height we have to deal with wanting +0% for the first row, and 100% for the last row. Normal maths +for a 10 row list would return 1/10 = 10% for the first row, so +we need to tweak the numbers to add an extra 10% somewhere. The +formula if we're trying to get to row 0 in a 10 row list (assuming our +index is zero based, so the last row is row 9) is: +
+ 0 + 0 / 10 = 0% ++ +
To get to row 9 (i.e. the last row) in the same list, we want to +go to: +
+ ( 9 + 1 ) / 10 = 100% ++So we need to apportion one whole row within the overall grid scroll, +the formula is: +
+ ( index + ( index / (total rows - 1) ) / total rows +
grid – {Grid} –
+the grid you'd like to act upon, usually available +from gridApi.grid
gridRow – {GridRow} –
+row to make visible
gridCol – {GridCol} –
+column to make visible
uiGridNavService
+(service in module ui.grid.cellNav
+)
+Services for editing features. If you don't like the key maps we use, +override with a service decorator (see angular docs)
Grid
+(class in module ui.grid
+)
+Grid is the main viewModel. Any properties or methods needed to maintain state are defined in +this prototype. One instance of Grid is created per Grid directive instance.
Grid(options);+
options – {object} –
+Object map of options to pass into the grid. An 'id' property is expected.
adds a row header column to the grid
column – {object} –
+def
uses the first row of data to assign colDef.type for any types not defined.
Populates columnDefs from the provided data
rowBuilder – {function(colDef, col, gridOptions)} –
+function to be called
creates GridColumn objects from the columnDefinition. Calls each registered +columnBuilder to further process the column
options – {object} –
+An object contains options to use when building columns
+ +buildColumns
will reorder existing columns according to the order within the column definitions.{Promise}
+– a promise to load any needed column resources
calls each styleComputation function
Calls the callbacks based on the type of data change that +has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the +event type is matching, or if the type is ALL.
type – {number} –
+the type of event that occurred - one of the +uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
refreshes the grid when a column refresh +is notified, which triggers handling of the visible flag. +This is called on uiGridConstants.dataChange.COLUMN, and is +registered as a dataChangeCallback in grid.js
name – {string} –
+column name
creates the left render container if it doesn't already exist
creates the right render container if it doesn't already exist
sets isScrollingHorizontally to true and sets it to false in a debounced function
sets isScrollingVertically to true and sets it to false in a debounced function
Gets the value of a cell for a particular row and column
row – {GridRow} –
+Row to access
col – {GridColumn} –
+Column to access
returns a grid colDef for the column name
name – {string} –
+column.field
returns a grid column for the column name
name – {string} –
+column name
Return the columns that the grid is currently being sorted by
{Array[GridColumn]}
+– An array of GridColumn objects
Returns the $parse-able accessor for a column within its $scope
col – {GridColumn} –
+col object
returns the GridRow that contains the rowEntity
rowEntity – {object} –
+the gridOptions.data array element instance
Triggered when the browser window resizes; automatically resizes the grid
returns true if leftContainer exists
returns true if rightContainer exists
returns true if leftContainer has columns
returns true if rightContainer has columns
Returns true if grid is RightToLeft
creates or removes GridRow objects from the newRawData array. Calls each registered +rowBuilder to further process the row
+ +Rows are identified using the gridOptions.rowEquality function
Notifies us that a data change has occurred, used in the public +api for users to tell us when they've changed data or some other event that +our watches cannot pick up
type – {string} –
+the type of event that occurred - one of the +uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
precompiles all cell templates
processes all RowBuilders for the gridRow
gridRow – {GridRow} –
+reference to gridRow
{GridRow}
+– the gridRow with all additional behavior added
calls the row processors, specifically +intended to reset the sorting when an edit is called, +registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
name – {string} –
+column name
todo: @c0bra can you document this method?
TBD
{promise}
+– promise that is resolved when the canvas +has been refreshed
Redraw the rows and columns based on our current scroll position
[rowsAdded] – {boolean} –
+Optional to indicate rows are added and the scroll percentage must be recalculated
Refresh the rendered grid on screen.
Refresh the rendered rows on screen? Note: not functional at present
{promise}
+– promise that is resolved when render completes?
When the build creates columns from column definitions, the columnbuilders will be called to add +additional properties to the column.
columnsProcessor – {function(colDef, col, gridOptions)} –
+function to be called
Register a "columns processor" function. When the columns are updated, +the grid calls each registered "columns processor", which has a chance +to alter the set of columns, as long as the count is not modified.
rows – {function(renderableColumns)} –
+processor function
{Array[GridColumn]}
+– Updated renderable columns
When a data change occurs, the data change callbacks of the specified type +will be called. The rules are:
+ +For a given event: +- ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks +- ROW calls ROW and ALL callbacks +- EDIT calls EDIT and ALL callbacks +- COLUMN calls COLUMN and ALL callbacks +- OPTIONS calls OPTIONS and ALL callbacks
callback – {function(grid)} –
+function to be called
types – {array} –
+the types of data change you want to be informed of. Values from +the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to +ALL
{function}
+– deregister function - a function that can be called to deregister this callback
When the build creates rows from gridOptions.data, the rowBuilders will be called to add +additional properties to the row.
rowBuilder – {function(row, gridOptions)} –
+function to be called
Register a "rows processor" function. When the rows are updated, +the grid calls each registered "rows processor", which has a chance +to alter the set of rows (sorting, etc) as long as the count is not +modified.
rows – {function(renderableRows)} –
+processor function
{Array[GridRow]}
+– Updated renderable rows
registered a styleComputation function
+ +If the function returns a value it will be appended into the grid's <style>
block
styleComputation – {function($scope)} –
+function
Remove a registered rows processor
rows – {function(renderableRows)} –
+processor function
Return the columns that the grid is currently being sorted by
[excludedColumn] – {GridColumn} –
+Optional GridColumn to exclude from having its sorting reset
Set the sorting on a given column, optionally resetting any existing sorting on the Grid. +Emits the sortChanged event whenever the sort criteria are changed.
column – {GridColumn} –
+Column to set the sorting on
[direction] – {uiGridConstants.ASC|uiGridConstants.DESC} –
+Direction to sort by, either descending or ascending. +If not provided, the column will iterate through the sort directions: ascending, descending, unsorted.
[add] – {boolean} –
+Add this column to the sorting. If not provided or set to false
, the Grid will reset any existing sorting and sort
+by this column only
{Promise}
+– A resolved promise that supplies the column.
flags all render containers to update their canvas height
reference to the application scope (the parent scope of the ui-grid element). Assigned in ui-grid controller
+
+use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
set to true when Grid is scrolling horizontally. Set to false via debounced method
set to true when Grid is scrolling vertically. Set to false via debounced method
set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells +us which direction we are scrolling. Set to NONE via debounced method
GridApi
+(class in module ui.grid
+)
+GridApi provides the ability to register public methods events inside the grid and allow
+for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
+
+To listen to events, you must add a callback to gridOptions.onRegisterApi
+
+ $scope.gridOptions.onRegisterApi = function(gridApi){ + gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){ + $log.log('navigation event'); + }); + }; +
GridApi(grid);+
grid – {object} –
+grid that owns api
Registers a new event for the given feature. The event will get a
+.raise and .on prepended to it
+
+.raise.eventName() - takes no arguments
+
+
+.on.eventName(scope, callBackFn, _this)
+
+scope - a scope reference to add a deregister call to the scopes .$on('destroy')
+
+callBackFn - The function to call
+
+_this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
+
+.on.eventName returns a dereg funtion that will remove the listener. It's not necessary to use it as the listener
+will be removed when the scope is destroyed.
featureName – {string} –
+name of the feature that raises the event
eventName – {string} –
+name of the event
Registers features and events from a simple objectMap. +eventObjectMap must be in this format (multiple features allowed) +
+{featureName: + { + eventNameOne:function(args){}, + eventNameTwo:function(args){} + } + } +
eventObjectMap – {object} –
+map of feature/event names
Registers a new event for the given feature
featureName – {string} –
+name of the feature
methodName – {string} –
+name of the method
callBackFn – {object} –
+function to execute
_this – {object} –
+binds callBackFn 'this' to _this. Defaults to gridApi.grid
Registers features and methods from a simple objectMap.
+eventObjectMap must be in this format (multiple features allowed)
+
+{featureName:
+ {
+ methodNameOne:function(args){},
+ methodNameTwo:function(args){}
+ }
eventObjectMap – {object} –
+map of feature/event names
_this – {object} –
+binds this to _this for all functions. Defaults to gridApi.grid
Used to execute a function while disabling the specified event listeners. +Disables the listenerFunctions, executes the callbackFn, and then enables +the listenerFunctions again
listenerFuncs – {object} –
+listenerFunc or array of listenerFuncs to suppress. These must be the same +functions that were used in the .on.eventName method
callBackFn – {object} –
+function to execute
+var navigate = function (newRowCol, oldRowCol){ + //do something on navigate +} + +gridApi.cellNav.on.navigate(scope,navigate); + + +//call the scrollTo event and suppress our navigate listener +//scrollTo will still raise the event for other listeners +gridApi.suppressEvents(navigate, function(){ + gridApi.cellNav.scrollTo(aRow, aCol); +}); + +
GridColumn
+(class in module ui.grid
+)
+Represents the viewModel for each column. Any state or methods needed for a Grid Column +are defined on this prototype
GridColumn(colDef, index, grid);+
colDef – {ColDef} –
+Column definition.
index – {number} –
+the current position of the column in the array
grid – {Grid} –
+reference to the grid
Initializes a gridColumn
colDef – {ColumnDef} –
+the column def to associate with this column
uid – {number} –
+the unique and immutable uid we'd like to allocate to this column
grid – {Grid} –
+the grid we'd like to create this column in
Gets the aggregation label from colDef.aggregationLabel if +specified or by using i18n, including deciding whether or not to display +based on colDef.aggregationHideLabel.
label – {string} –
+the i18n lookup value to use for the column label
gets the aggregation value based on the aggregation type for this column
Returns the class name for the column
prefixDot – {bool} –
+if true, will return .className instead of className
Returns the class definition for th column
Returns the render container object that this column belongs to.
+ +Columns will be default be in the body
render container if they aren't allocated to one specifically.
Hides the column by setting colDef.visible = false
Sets a property on the column using the passed in columnDef, and +setting the defaultValue if the value cannot be found on the colDef
colDef – {ColumnDef} –
+the column def to look in for the property value
propName – {string} –
+the property name we'd like to set
defaultValue – {object} –
+the value to use if the colDef doesn't provide the setting
Makes the column visible by setting colDef.visible = true
Moves settings from the columnDef down onto the column, +and sets properties as appropriate
colDef – {ColumnDef} –
+the column def to look in for the property value
isNew – {boolean} –
+whether the column is being newly created, if not +we're updating an existing column, and some items such as the sort shouldn't +be copied down
Column name that will be shown in the header. If displayName is not +provided then one is generated using the name.
field must be provided if you wish to bind to a
+property in the data source. Should be an angular expression that evaluates against grid.options.data
+array element. Can be a complex expression: employee.address.city
, or can be a function: employee.getFullAddress()
.
+See the angular docs on binding expressions.
Filter on this column.
{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...', flags: { caseSensitive: false } }
Filters for this column. Includes 'term' property bound to filter input elements.
[ +{ + term: 'foo', // ngModel for <input> + condition: uiGridConstants.filter.STARTS_WITH, + placeholder: 'starts with...', + flags: { caseSensitive: false } +}, +{ + term: 'baz', + condition: uiGridConstants.filter.ENDS_WITH, + placeholder: 'ends with...' +} +]
(mandatory) each column should have a name, although for backward +compatibility with 2.x name can be omitted if field is present
Algorithm to use for sorting this column. Takes 'a' and 'b' parameters +like any normal sorting function.
GridEvents
+(class in module ui.grid
+)
+GridEvents provides the ability to register public events inside the grid and allow +for other components to subscribe to these events via featureName.on.eventName(function(args){}
GridEvents(grid);+
grid – {object} –
+grid that owns these events
Registers a new event for the given feature
featureName – {string} –
+name of the feature that raises the event
eventName – {string} –
+name of the event
Registers features and events from a simple objectMap.
+eventObjectMap must be in this format (multiple features allowed)
+
+{featureName:
+ {
+ eventNameOne:function(args){},
+ eventNameTwo:function(args){}
+ }
eventObjectMap – {object} –
+map of feature/event names
columnDef
+(service in module ui.grid.class:GridOptions
+)
+Definition / configuration of an individual column, which would typically be +one of many column definitions within the gridOptions.columnDefs array
defaults to false, if set to true hides the label text +in the aggregation footer, so only the value is displayed.
cellClass can be a string specifying the class to append to a cell +or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
cellFilter is a filter to apply to the content of each cell
+gridOptions.columnDefs[0].cellFilter = 'date'
a custom template for each cell in this column. The default +is ui-grid/uiGridCell. If you are using the cellNav feature, this template +must contain a div that can receive focus.
Column name that will be shown in the header. If displayName is not +provided then one is generated using the name.
turn off filtering for an individual column, where +you've turned on filtering for the overall grid
+gridOptions.columnDefs[0].enableFiltering = false;
(optional) True by default. When set to false, this setting prevents a user from hiding the column +using the column menu or the grid menu.
(optional) True by default. When enabled, this setting adds sort +widgets to the column header, allowing sorting of the data in the individual column.
field must be provided if you wish to bind to a
+property in the data source. Should be an angular expression that evaluates against grid.options.data
+array element. Can be a complex expression: employee.address.city
, or can be a function: employee.getFullAddress()
. * See the angular docs on binding expressions. *
Specify a single filter field on this column.
+ +A filter consists of a condition, a term, and a placeholder:
+ +<input>.placeholder
attribute.caseSensitive
, set to false if you don't want
+case sensitive matching$scope.gridOptions.columnDefs = [ +{ + field: 'field1', + filter: { + term: 'xx', + condition: uiGridConstants.filter.STARTS_WITH, + placeholder: 'starts with...', + flags: { caseSensitive: false } + } +} +];
Specify multiple filter fields.
$scope.gridOptions.columnDefs = [ +{ + field: 'field1', filters: [ + { + term: 'aa', + condition: uiGridConstants.filter.STARTS_WITH, + placeholder: 'starts with...', + flags: { caseSensitive: false } + }, + { + condition: uiGridConstants.filter.ENDS_WITH, + placeholder: 'ends with...' + } + ] +} +];
headerCellClass can be a string specifying the class to append to a cell +or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
headerCellFilter is a filter to apply to the content of the column header
+gridOptions.columnDefs[0].headerCellFilter = 'translate'
a custom template for the header for this column. The default +is ui-grid/uiGridHeaderCell
sets the maximum column width. Should be a number.
$scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}];
sets the minimum column width. Should be a number.
$scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}];
(mandatory) each column should have a name, although for backward +compatibility with 2.x name can be omitted if field is present
Can be used to set the sort direction for the column, values are +uiGridConstants.ASC or uiGridConstants.DESC
$scope.gridOptions.columnDefs = [ { field: 'field1', sort: { direction: uiGridConstants.ASC }}]
Algorithm to use for sorting this column. Takes 'a' and 'b' parameters +like any normal sorting function.
(optional) False by default. When enabled, this setting hides the removeSort option +in the menu.
the type of the column, used in sorting. If not provided then the +grid will guess the type. Add this only if the grid guessing is not to your +satisfaction. Refer to gridUtil.guessType for +a list of values the grid knows about.
sets whether or not the column is visible +Default is true
$scope.gridOptions.columnDefs = [ +{ field: 'field1', visible: true}, +{ field: 'field2', visible: false } +];
sets the column width. Can be either +a number or a percentage, or an * for auto.
$scope.gridOptions.columnDefs = [ { field: 'field1', width: 100}, +{ field: 'field2', width: '20%'}, +{ field: 'field3', width: '*' }];
{name:'field1', field: 'field1', filter: { term: 'xxx' }}
GridOptions
+(class in module ui.grid
+)
+Default GridOptions class. GridOptions are defined by the application developer and overlaid +over this object. Setting gridOptions within your controller is the most common method for an application +developer to configure the behaviour of their ui-grid
GridOptions();+
This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
+ +By default it returns the $$hashKey
property but can be overridden to use any property or set of properties you want.
By default, rows are compared using object equality. This option can be overridden +to compare on any data item property or function
entityA – {object} –
+First Data Item to compare
entityB – {object} –
+Second Data Item to compare
This function is used to get and, if necessary, set the value uniquely identifying this row (i.e. if an identity is not present it will set one).
+ +By default it returns the $$hashKey
property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
by default, the parent scope of the ui-grid element will be assigned to grid.appScope +this property allows you to assign any reference you want to grid.appScope
Array of columnDef objects. Only required property is name. +The individual options available in columnDefs are documented in the +columnDef section +field property can be used in place of name for backwards compatibility with 2.x
var columnDefs = [{name:'field1'}, {name:'field2'}];
Turn virtualization on when number of columns goes over this number, defaults to 10
(mandatory) Array of data to be rendered into the grid, providing the data source or data binding for +the grid. The most common case is an array of objects, where each object has a number of attributes. +Each attribute automatically becomes a column in your grid. This array could, for example, be sourced from +an angularJS $resource query request. The array can also contain complex objects.
False by default. When enabled, this setting adds filter +boxes to each column header, allowing filtering within the column for the entire grid. +Filtering can then be disabled on individual columns using the columnDefs.
True by default. When enabled, this setting allows uiGrid to add
+$$hashKey
-type properties (similar to Angular) to elements in the data
array. This allows
+the grid to maintain state while vastly speeding up the process of altering data
by adding/moving/removing rows.
Note that this DOES add properties to your data that you may not want, but they are stripped out when using angular.toJson()
. IF
+you do not want this at all you can disable this setting but you will take a performance hit if you are using large numbers of rows
+and are altering the data set often.
True by default. When enabled, this setting adds sort +widgets to the column headers, allowing sorting of the data for the entire grid. +Sorting can then be disabled on individual columns using the columnDefs.
Extra columns to to render outside of the viewport, which helps with smoothness of scrolling. +Defaults to 4
Extra rows to to render outside of the viewport, which helps with smoothness of scrolling. +Defaults to 4
Array of property names in data to ignore when auto-generating column names. Provides the +inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns +to exclude.
+ +If columnDefs is defined, this will be ignored.
+ +Defaults to ['$$hashKey']
The height of the header in pixels, defaults to 30
Null by default. When provided, this setting uses a custom header +template, rather than the default template. Can be set to either the name of a template file: +
$scope.gridOptions.headerTemplate = 'header_template.html';+inline html +
$scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'+or the id of a precompiled template (TBD how to use this).
$scope.gridOptions.headerTemplate = '<div></div>';+ +
If you want to only have a static header, then you can set to static content. If +you want to tailor the existing column headers, then you should look at the +current 'ui-grid-header.html' template in github as your starting point.
Defaults to 4
This setting controls at what percentage of the scroll more data +is requested by the infinite scroll
Defaults to 200
Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
Columns can't be smaller than this, defaults to 10 pixels
A callback that returns the gridApi once the grid is instantiated, which is +then used to interact with the grid programatically.
+ +Note that the gridApi.core.renderingComplete event is identical to this +callback, but has the advantage that it can be called from multiple places +if needed
+$scope.gridOptions.onRegisterApi = function ( gridApi ) { + $scope.gridApi = gridApi; + $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid ); +}; +
The height of the row in pixels, defaults to 30
'ui-grid/ui-grid-row' by default. When provided, this setting uses a +custom row template. Can be set to either the name of a template file: +
$scope.gridOptions.rowTemplate = 'row_template.html';+inline html +
$scope.gridOptions.rowTemplate = '<div style="background-color: aquamarine" ng-click="grid.appScope.fnOne(row)" ng-repeat="col in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ui-grid-cell></div>';+or the id of a precompiled template (TBD how to use this) can be provided.
Defaults to 4
Default time to throttle scroll events to, defaults to 70ms
True by default. When set to false, this setting will replace the +standard header template with '
It will also set the headerRowHeight
option to 0.
False by default. When enabled, this setting suppresses the internal filtering. +All UI logic will still operate, allowing filter conditions to be set and modified.
+ +The external filter logic can listen for the filterChange
event, which fires whenever
+a filter has been adjusted.
Prevents the internal sorting from executing. Events will +still be fired when the sort changes, and the sort information on +the columns will be updated, allowing an external sorter (for example, +server sorting) to be implemented. Defaults to false.
Turn virtualization on when number of data elements goes over this number, defaults to 20
To define your gridOptions within your controller: +
$scope.gridOptions = { + data: $scope.myData, + columnDefs: [ + { name: 'field1', displayName: 'pretty display name' }, + { name: 'field2', visible: false } + ] +};+ +
You can then use this within your html template, when you define your grid: +
<div ui-grid="gridOptions"></div>+ +
To provide default options for all of the grids within your application, use an angular +decorator to modify the GridOptions factory. +
+app.config(function($provide){ + $provide.decorator('GridOptions',function($delegate){ + var gridOptions; + gridOptions = angular.copy($delegate); + gridOptions.initialize = function(options) { + var initOptions; + initOptions = $delegate.initialize(options); + initOptions.enableColumnMenus = false; + return initOptions; + }; + return gridOptions; + }); +}); +
GridRenderContainer
+(class in module ui.grid
+)
+The grid has render containers, allowing the ability to have pinned columns. If the grid +is right-to-left then there may be a right render container, if left-to-right then there may +be a left render container. There is always a body render container.
GridRenderContainer(name, grid, options);+
name – {string} –
+The name of the render container ('body', 'left', or 'right')
grid – {Grid} –
+the grid the render container is in
options – {object} –
+the render container options
Returns the total canvas height. Only recalculates if canvasHeightShouldUpdate = false
{number}
+– total height of all the visible rows in the container
last calculated canvas height value
flag to signal that container should recalculate the canvas size
GridRow
+(class in module ui.grid
+)
+GridRow is the viewModel for one logical row on the grid. A grid Row is not necessarily a one-to-one +relation to gridOptions.data.
GridRow(entity, index, reference);+
entity – {object} –
+the array item from GridOptions.data
index – {number} –
+the current position of the row in the array
reference – {Grid} –
+to the parent grid
Clears any override on the row visibility, returning it +to normal visibility calculations. If the row is currently invisible +then sets it to visible and calls refresh and emits the rowsVisibleChanged +event +TODO: if filter in action, then is this right?
row – {GridRow} –
+row clear force invisible, needs to be a GridRow, +which can be found from your data entity using grid.findRow
returns the qualified field name minus the row path +ie: entity.fieldA
col – {GridCol} –
+column instance
{string}
+– resulting name that can be evaluated against a row
returns the qualified field name as it exists on scope +ie: row.entity.fieldA
col – {GridCol} –
+column instance
{string}
+– resulting name that can be evaluated on scope
Sets an override on the row that forces it to always +be invisible, and if the row is currently visible then marks it +as invisible and refreshes the grid. Emits the rowsVisibleChanged +event if it changed the row visibility
row – {GridRow} –
+row to force invisible, needs to be a GridRow, +which can be found from your data entity using grid.findRow
A reference to an item in gridOptions.data[]
A reference back to the grid
height of each individual row. changing the height will flag all +row renderContainers to recalculate their canvas height
UniqueId of row
If true, the row will be rendered
RowSorter
+(class in module ui.grid
+)
+RowSorter provides the default sorting mechanisms, +including guessing column types and applying appropriate sort +algorithms
Sorts any values that provide the < method, including strings +or numbers. Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
Get the sort function for the column. Looks first in +rowSorter.colSortFnCache using the column name, failing that it +looks at col.sortingAlgorithm (and puts it in the cache), failing that +it guesses the sort algorithm based on the data type.
+ +The cache currently seems a bit pointless, as none of the work we do is +processor intensive enough to need caching. Presumably in future we might +inspect the row data itself to guess the sort function, and in that case +it would make sense to have a cache, the infrastructure is in place to allow +that.
grid – {Grid} –
+the grid to consider
col – {GridCol} –
+the column to find a function for
rows – {array} –
+an array of grid rows. Currently unused, but presumably in future +we might inspect the rows themselves to decide what sort of data might be there
{function}
+– the sort function chosen for the column
Assigns a sort function to use based on the itemType in the column
itemType – {string} –
+one of 'number', 'boolean', 'string', 'date', 'object'. And +error will be thrown for any other type.
{function}
+– a sort function that will sort that type
Sorts nulls and undefined to the bottom (top when +descending). Called by each of the internal sorters before +attempting to sort. Note that this method is available on the core api +via gridApi.core.sortHandleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– null if there were no nulls/undefineds, otherwise returns +a sort value that should be passed back from the sort function
Used where multiple columns are present in the sort criteria, +we determine which column should take precedence in the sort by sorting +the columns based on their sort.priority
a – {gridColumn} –
+column a
b – {gridColumn} –
+column b
{number}
+– normal sort function, returns -ve, 0, +ve
sorts the grid
grid – {Object} –
+the grid itself
rows – {Object} –
+the rows to be sorted
columns – {Object} –
+the columns in which to look +for sort criteria
Sorts string values. Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
Sorts boolean values, true is considered larger than false. +Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
Sorts date values. Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
Sorts numerical values. Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
+Handles nulls and undefined through calling handleNulls
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– normal sort function, returns -ve, 0, +ve
ScrollEvent
+(class in module ui.grid
+)
+Model for all scrollEvents
ScrollEvent(grid, sourceRowContainer, sourceColContainer, source);+
grid – {Grid} –
+that owns the scroll event
sourceRowContainer – {GridRenderContainer} –
+that owns the scroll event. Can be null
sourceColContainer – {GridRenderContainer} –
+that owns the scroll event. Can be null
source – {string} –
+the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
fires an event using grid.api.core.raise.scrollEvent
fires a throttled event using grid.api.core.raise.scrollEvent
returns newScrollLeft property if available; calculates a new value if it isn't
returns newScrollTop property if available; calculates a new value if it isn't
the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
A reference back to the grid
PublicApi
+(api in module ui.grid.core
+)
+Public Api for the core grid features
adds a row header column to the grid
column – {object} –
+def
Clears any override on visibility for the row so that it returns to
+using normal filtering and other visibility calculations.
+If the row is currently invisible then sets it to visible and calls
+both grid refresh and emits the rowsVisibleChanged event
+TODO: if a filter is active then we can't just set it to visible?
rowEntity – {object} –
+gridOptions.data[] array instance
The visibility of a column has changed, +the column itself is passed out as a parameter of the event.
column – {GridCol} –
+the column that changed
+gridApi.core.on.columnVisibilityChanged( $scope, function (column) { + // do something +} ); +
Returns all visible rows
grid – {Grid} –
+the grid you want to get visible rows from
{array}
+– an array of gridRow
Trigger a grid resize, normally this would be picked +up by a watch on window size, but in some circumstances it is necessary +to call this manually
{promise}
+– promise that is resolved when render completes?
Notify the grid that a data or config change has occurred, +where that change isn't something the grid was otherwise noticing. This +might be particularly relevant where you've changed values within the data +and you'd like cell classes to be re-evaluated, or changed config within +the columnDef and you'd like headerCellClasses to be re-evaluated.
type – {string} –
+one of the +uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells +us which refreshes to fire.
Refresh the rendered grid on screen.
Refresh the rendered grid on screen? Note: not functional at present
{promise}
+– promise that is resolved when render completes?
Rendering is complete, called at the same
+time as onRegisterApi
, but provides a way to obtain
+that same event within features without stopping end
+users from getting at the onRegisterApi method.
Included in gridApi so that it's always there - otherwise +there is still a timing problem with when a feature can +call this.
gridApi – {GridApi} –
+the grid api, as normally +returned in the onRegisterApi method
+gridApi.core.on.renderingComplete( grid ); +
Sets an override on the row to make it always invisible,
+which will override any filtering or other visibility calculations.
+If the row is currently visible then sets it to invisible and calls
+both grid refresh and emits the rowsVisibleChanged event
rowEntity – {object} –
+gridOptions.data[] array instance
The sort criteria on one or more columns has +changed. Provides as parameters the grid and the output of +getColumnSorting, which is an array of gridColumns +that have sorting on them, sorted in priority order.
grid – {Grid} –
+the grid
sortColumns – {array} –
+an array of columns with +sorts on them, in priority order
+gridApi.core.on.sortChanged( grid, sortColumns ); +
A null handling method that can be used when building custom sort +functions
a – {object} –
+sort value a
b – {object} –
+sort value b
{number}
+– null if there were no nulls/undefineds, otherwise returns +a sort value that should be passed back from the sort function
+mySortFn = function(a, b) { +var nulls = $scope.gridApi.core.sortHandleNulls(a, b); +if ( nulls !== null ){ + return nulls; +} else { + // your code for sorting here +}; +
is raised when the canvas height has changed
+
+arguments: oldHeight, newHeight
is raised after the filter is changed. The nature +of the watch expression doesn't allow notification of what changed, +so the receiver of this event will need to re-extract the filter +conditions from the columns.
+ +is raised after the cache of visible rows is changed.
+ +is raised after the rows that are visible +change. The filtering is zero-based, so it isn't possible +to say which rows changed (unlike in the selection feature). +We can plausibly know which row was changed when setRowInvisible +is called, but in that situation the user already knows which row +they changed. When a filter runs we don't know what changed, +and that is the one that would have been useful.
+ +is raised on a scroll Event. Called frequently so be careful what you do with it
+ +core
+(service in module ui.grid
+)
+Not sure this needs to be defined, I'll see
uiGrid
+(directive in module ui.grid
+)
+Create a very basic grid.
<ui-grid + uiGrid="{Object}"> +</ui-grid>+as attribute
<div ui-grid="{Object}"> + ... +</div>+
uiGrid – {Object} –
+Options for the grid to use
uiGridColumnMenu
+(directive in module ui.grid
+)
+Allows us to interpolate expressions in <style>
elements. Angular doesn't do this by default as it can/will/might? break in IE8.
<style ui-grid-column-menu> + ... +</style>+
uiGridStyle
+(directive in module ui.grid
+)
+Allows us to interpolate expressions in <style>
elements. Angular doesn't do this by default as it can/will/might? break in IE8.
<style ui-grid-style> + ... +</style>+
gridTest
+(api in module ui.grid.e2eTestLibrary
+)
+End to end test functions. Whenever these are updated, it may also be necessary +to update the associated tutorial.
Cancels the filter in a column
gridId – {string} –
+the id of the grid that you want to inspect
colNumber – {integer} –
+the number of the column (within the visible columns) +that you want to cancel the filter in
+gridTestUtils.cancelFilterInColumn('myGrid', 0); +
Clicks on the header of the specified column, +which would usually result in a sort
gridId – {string} –
+the id of the grid that you want to inspect
colNumber – {integer} –
+the number of the column (within the visible columns) +that you want to click on
+gridTestUtils.clickHeaderCell('myGrid', 0); +
Internal method used to return a dataCell element +given the grid and column, note it only returns from the 'body' +render container
gridId – {string} –
+the id of the grid that you want to inspect
fetchRow – {integer} –
+the number of the row (within the visible rows) +that you want to return
fetchCol – {integer} –
+the number of the col (within the visible cols) +that you want to return
+myElement = gridTestUtils.dataCell('myGrid', 2, 2); +
Enters a filter in a column
gridId – {string} –
+the id of the grid that you want to inspect
colNumber – {integer} –
+the number of the column (within the visible columns) +that you want to enter the filter in
filterValue – {string} –
+the value you want to enter into the filter
+gridTestUtils.cancelFilterInColumn('myGrid', 0); +
Checks that a cell matches the specified value, +takes a regEx or a simple string.
gridId – {string} –
+the id of the grid that you want to inspect
expectedRow – {integer} –
+the number of the row (within the visible rows) +that you want to check the value of
expectedCol – {integer} –
+the number of the column (within the visible columns) +that you want to check the value of
expectedValue – {string} –
+a regex or string of the value you expect in that cell
+gridTestUtils.expectCellValueMatch('myGrid', 0, 2, 'CellValue'); +
Checks that a filter box exists in the specified column
gridId – {string} –
+the id of the grid that you want to inspect
colNumber – {integer} –
+the number of the column (within the visible columns) +that you want to verify the filter box is in
count – {integer} –
+the number filter boxes you expect - 0 meaning none, 1 meaning +a standard filter, 2 meaning a numerical filter with greater than / less than.
+gridTestUtils.expectFilterBoxInColumn('myGrid', 0, 0); +
Checks that a header cell matches the specified value, +takes a regEx or a simple string.
gridId – {string} –
+the id of the grid that you want to inspect
expectedCol – {integer} –
+the number of the column (within the visible columns) +that you want to check the value of
expectedValue – {string} –
+a regex or string of the value you expect in that header
+gridTestUtils.expectHeaderCellValueMatch('myGrid', 2, 'HeaderValue'); +
Checks that a grid header body render container (the default render container) +has the specified number of columns. If you are using pinned columns then you may also want +to check expectHeaderLeftColumnCount
gridId – {string} –
+the id of the grid that you want to inspect
expectedNumCols – {integer} –
+the number of visible columns you expect the +body to have
+gridTestUtils.expectHeaderColumnCount('myGrid', 2); +
Checks that a grid header left render container has the specified number of columns.
gridId – {string} –
+the id of the grid that you want to inspect
expectedNumCols – {integer} –
+the number of visible columns you expect the +left render container to have
+gridTestUtils.expectHeaderLeftColumnCount('myGrid', 2); +
Checks that a grid has the specified number of rows. Note +that this only returns the number of rendered rows, and the grid does +row virtualisation - that is that the browser can only see the rendered +rows, not all the rows in the dataset. This method is useful when doing +functional tests with small numbers of data, but typically with numbers +greater than about 10 you'll find that some of the rows are not rendered +and therefore an error is given.
gridId – {string} –
+the id of the grid that you want to inspect
expectedNumRows – {integer} –
+the number of visible rows you expect the +grid to have
+gridTestUtils.expectRowCount('myGrid', 2); +
Checks that a row matches the specified values, +takes an array of regExes or simple strings.
gridId – {string} –
+the id of the grid that you want to inspect
expectedRow – {integer} –
+the number of the row (within the visible rows) +that you want to check the value of
expectedValueArray – {array} –
+an array of regexes or strings of the values you expect in that row
+gridTestUtils.expectRowValuesMatch('myGrid', 0, [ 'CellValue1', '^cellvalue2', 'cellValue3$' ]); +
Internal method used to return a headerCell element +given the grid and column
gridId – {string} –
+the id of the grid that you want to inspect
col – {integer} –
+the number of the column (within the visible columns) +that you want to return
+gridTestUtils.headerCell('myGrid', 2); +
Drags the left resizer border towards the column menu button, +which will perform a column resizing.
gridId – {string} –
+the id of the grid that you want to adjust
colNumber – {integer} –
+the number of the column (within the visible columns) +which left resizer border you wish to drag (this will increase the size of colNumber-1).
+gridTestUtils.resizeHeaderCell('myGrid', 1); +
Shift-clicks on the header of the specified column, +which would usually result in adding a second column to the sort
gridId – {string} –
+the id of the grid that you want to inspect
colNumber – {integer} –
+the number of the column (within the visible columns) +that you want to click on
+gridTestUtils.shiftClickHeaderCell('myGrid', 0); +
+
+End to end test functions. Whenever these are updated, it may also be necessary +to update the associated tutorial.
ColDef
+(api in module ui.grid.edit
+)
+Column Definitions for edit feature
ColumnDef
+(api in module ui.grid.edit
+)
+Column Definition for edit feature, these are available to be +set using the ui-grid gridOptions.columnDefs
If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
+function($scope){ + //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed + return true; +} +
A filter that you would like to apply to the values in the options list
+of the dropdown. For example if you were using angular-translate you might set this
+to 'translate'
+$scope.gridOptions = { + columnDefs: [ + {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', + editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}], + editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' } + ], +
the label for the "id" field +in the editDropdownOptionsArray. Defaults +to 'id'
+$scope.gridOptions = { + columnDefs: [ + {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', + editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}], + editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' } + ], +
the label for the "value" field +in the editDropdownOptionsArray. Defaults +to 'value'
+$scope.gridOptions = { + columnDefs: [ + {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor', + editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}], + editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' } + ], +
cell template to be used when editing this column. Can be Url or text template
+
Defaults to gridOptions.editableCellTemplate
enable editing on column
If true, then editor is invoked as soon as cell receives focus. Default false.
+
requires both the cellNav feature and the edit feature to be enabled
GridOptions
+(api in module ui.grid.edit
+)
+Options for configuring the edit feature, these are available to be +set using the ui-grid gridOptions
If specified, either a value or function to be used by all columns before editing. +If falsy, then editing of cell is not allowed.
+function($scope){ + //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed + return true; +} +
If specified, cellTemplate to use as the editor for all columns.
+
defaults to 'ui-grid/cellTextEditor'
If defined, sets the default value for the editable flag on each individual colDefs +if their individual enableCellEdit configuration is not defined. Defaults to undefined.
If true, then editor is invoked as soon as cell receives focus. Default false.
+
requires cellNav feature and the edit feature to be enabled
GridRow
+(api in module ui.grid.edit
+)
+GridRow options for edit feature, these are available to be +set internally only, by other features
enable editing on row, grouping for example might disable editing on group header rows
PublicApi
+(api in module ui.grid.edit
+)
+Public Api for edit feature
raised when cell editing is complete +
+ gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){}) ++ +
rowEntity – {object} –
+the options.data element that was edited
colDef – {object} –
+the column that was edited
newValue – {object} –
+new value
oldValue – {object} –
+old value
raised when cell editing starts on a cell +
+ gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){}) ++ +
rowEntity – {object} –
+the options.data element that was edited
colDef – {object} –
+the column that was edited
raised when cell editing is cancelled on a cell +
+ gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){}) ++ +
rowEntity – {object} –
+the options.data element that was edited
colDef – {object} –
+the column that was edited
uiGridEditConstants
+(constant in module ui.grid.edit
+)
+constants available in edit module
input
+(directive in module ui.grid.edit
+)
+directive to provide binding between input[date] value and ng-model for angular 1.2 +It is similar to input[date] directive of angular 1.3
+ +Supported date format for input is 'yyyy-MM-dd' +The directive will set the $valid property of input element and the enclosing form to false if +model is invalid date or value of input is entered wrong.
<input> +</input>+
uiGridCell
+(directive in module ui.grid.edit
+)
+Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell +Editing Actions.
+ +Binds edit start events to the uiGridCell element. When the events fire, the gridCell element is appended +with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
+ +The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN_CELL_EDIT angular event +and do the initial steps needed to edit the cell (setfocus on input element, etc).
+ +When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.) +it should emit the uiGridEditConstants.events.END_CELL_EDIT event.
+ +If editableCellTemplate recognizes that the editing has been cancelled (esc key) +it should emit the uiGridEditConstants.events.CANCEL_CELL_EDIT event. The original value +will be set back on the model by the uiGridCell directive.
+ +Events that invoke editing: + - dblclick + - F2 keydown (when using cell selection)
+ +Events that end editing: + - Dependent on the specific editableCellTemplate + - Standards should be blur and enter keydown
+ +Events that cancel editing: + - Dependent on the specific editableCellTemplate + - Standards should be Esc keydown
+ +Grid Events that end editing: + - uiGridConstants.events.GRID_SCROLL
<div ui-grid-cell> + ... +</div>+
uiGridEdit
+(directive in module ui.grid.edit
+)
+Adds editing features to the ui-grid directive.
<div ui-grid-edit> + ... +</div>+
uiGridEditDropdown
+(directive in module ui.grid.edit
+)
+dropdown editor for editable fields. +Provides EndEdit and CancelEdit events
+ +Events that end editing: + blur and enter keydown, and any left/right nav
+ +Events that cancel editing: + - Esc keydown
<div ui-grid-edit-dropdown> + ... +</div>+
uiGridEditor
+(directive in module ui.grid.edit
+)
+input editor directive for editable fields. +Provides EndEdit and CancelEdit events
+ +Events that end editing: + blur and enter keydown
+ +Events that cancel editing: + - Esc keydown
<div ui-grid-editor> + ... +</div>+
uiGridTextEditor
+(directive in module ui.grid.edit
+)
+input editor directive for editable fields. +Provides EndEdit and CancelEdit events
+ +Events that end editing: + blur and enter keydown
+ +Events that cancel editing: + - Esc keydown
<div ui-grid-text-editor> + ... +</div>+
+
+This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
+a keyboard.
+
+
+To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
+user to key data and then tab, arrow, or enter to the cells beside or below.
uiGridEditService
+(service in module ui.grid.edit
+)
+Services for editing features
columnBuilder function that adds edit properties to grid column
{promise}
+– promise that will load any needed templates when resolved
Determines if a keypress should start editing. Decorate this service to override with your +own key events. See service decorator in angular docs.
evt – {Event} –
+keydown event
{boolean}
+– true if an edit should start
uiGridExpandableService
+(service in module ui.grid.edit
+)
+Services for the expandable grid
GridOptions
+(api in module ui.grid.expandable
+)
+Options for configuring the expandable feature, these are available to be
+set using the ui-grid gridOptions
Width in pixels of the expandable column. Defaults to 40
+$scope.gridOptions = { + expandableRowHeaderWidth: 40 +} +
PublicApi
+(api in module ui.grid.expandable
+)
+Public Api for expandable feature
Collapse all subgrids. +
+ gridApi.expandable.collapseAllRows(); +
Toggle all subgrids. +
+ gridApi.expandable.toggleAllRows(); +
Toggle a specific row +
+ gridApi.expandable.toggleRowExpansion(rowEntity); +
rowEntity – {object} –
+the data entity for the row you want to expand
Grid
+(class in module ui.grid.expandable
+)
+Additional Grid properties added by expandable module
<ANY grid> + ... +</ANY>+as class
<ANY class="grid"> + ... +</ANY>+
reference to the expanded parent row that owns this grid
uiGrid
+(directive in module ui.grid.expandable
+)
+stacks on the uiGrid directive to register child grid with parent row when child is created
<ANY ui-grid> + ... +</ANY>+as class
<ANY class="ui-grid"> + ... +</ANY>+
uiGridExpandableRow
+(directive in module ui.grid.expandable
+)
+directive to render the expandable row template
<ANY ui-grid-expandable-row> + ... +</ANY>+as class
<ANY class="ui-grid-expandable-row"> + ... +</ANY>+
uiGridRow
+(directive in module ui.grid.expandable
+)
+stacks on the uiGridRow directive to add support for expandable rows
<ANY ui-grid-row> + ... +</ANY>+as class
<ANY class="ui-grid-row"> + ... +</ANY>+
uiGridViewport
+(directive in module ui.grid.expandable
+)
+stacks on the uiGridViewport directive to append the expandable row html elements to the +default gridRow template
<ANY ui-grid-viewport> + ... +</ANY>+as class
<ANY class="ui-grid-viewport"> + ... +</ANY>+
+
+This module provides the ability to create subgrids with the ability to expand a row +to show the subgrid.
+ +uiGridExpandableService
+(service in module ui.grid.expandable
+)
+Services for the expandable grid
ColumnDef
+(api in module ui.grid.exporter
+)
+ColumnDef settings for exporter
the alignment you'd like for this specific column when +exported into a pdf. Can be 'left', 'right', 'center' or any other +valid pdfMake alignment option.
Suppresses export for this column. Used by selection and expandable.
columnDef
+(service in module ui.grid.exporter.api:GridOptions
+)
+ColumnDef settings for exporter
the alignment you'd like for this specific column when +exported into a pdf. Can be 'left', 'right', 'center' or any other +valid pdfMake alignment option.
GridOptions
+(api in module ui.grid.exporter
+)
+GridOptions for exporter feature, these are available to be
+set using the ui-grid gridOptions
The character to use as column separator
+link
+
Defaults to ','
The default filename to use when saving the downloaded csv.
+This will only work in some browsers.
+
Defaults to 'download.csv'
A function to call for each field before exporting it. Allows +massaging of raw data into a display format, for example if you have applied +filters to convert codes into decodes, or you require +a specific date format in the exported content.
+ +The method is called once for each field exported, and provides the grid, the +gridCol and the GridRow for you to use as context in massaging the data.
{object}
+– you must return the massaged value ready for exporting
+gridOptions.exporterFieldCallback = function ( grid, row, col, value ){ + if ( col.name === 'status' ){ + value = decodeStatus( value ); + } + return value; +} +
A function to apply to the header displayNames before exporting. Useful for internationalisation,
+for example if you were using angular-translate you'd set this to $translate.instant
. Note that this
+call must be synchronous, it cannot be a call that returns a promise.
Behaviour can be changed to pass in name
instead of displayName
through use of exporterHeaderFilterUseName: true
.
+gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; }; ++OR +
+gridOptions.exporterHeaderFilter = $translate.instant; +
Defaults to false, which leads to displayName
being passed into the headerFilter.
+If set to true, then will pass name
instead.
+gridOptions.exporterHeaderFilterUseName = true; +
A custom callback routine that changes the pdf document, adding any +custom styling or content that is supported by pdfMake. Takes in the complete docDefinition, and +must return an updated docDefinition ready for pdfMake.
In this example we add a style to the style array, so that we can use it in our +footer definition. +
+ gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) { + docDefinition.styles.footerStyle = { bold: true, fontSize: 10 }; + return docDefinition; + } + + gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' } +
The default style in pdfMake format
+
Defaults to:
+
+ { + fontSize: 11 + } +
The header section for pdf exports. Can be +simple text: +
+ gridOptions.exporterPdfHeader = 'My Header'; ++Can be a more complex object in pdfMake format: +
+ gridOptions.exporterPdfHeader = { + columns: [ + 'Left part', + { text: 'Right part', alignment: 'right' } + ] + }; ++Or can be a function, allowing page numbers and the like +
+ gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; }; +
The maxium grid width - the current grid width
+will be scaled to match this, with any fixed width columns
+being adjusted accordingly.
+
Defaults to 720 (for A4 landscape), use 670 for LETTER
The orientation, should be a valid pdfMake value,
+'landscape' or 'portrait'
+
Defaults to landscape
The orientation, should be a valid pdfMake
+paper size, usually 'A4' or 'LETTER'
+pdfMake page sizes
+
Defaults to A4
The tableHeader style in pdfMake format
+
Defaults to:
+
+ { + bold: true, + fontSize: 12, + color: 'black' + } +
A tableLayout in pdfMake format,
+controls gridlines and the like. We use the default
+layout usually.
+
Defaults to null, which means no layout
The table style in pdfMake format
+
Defaults to:
+
+ { + margin: [0, 5, 0, 15] + } +
Columns that should not be exported. The selectionRowHeader is already automatically
+suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
+output then add it in this list. You should provide an array of column names.
+
Defaults to: []
+
+ gridOptions.exporterSuppressColumns = [ 'buttons' ]; +
GridRow
+(api in module ui.grid.exporter
+)
+GridRow settings for exporter
If set to false, then don't export this row, notwithstanding visible or
+other settings
+
Defaults to true
PublicApi
+(api in module ui.grid.exporter
+)
+Public Api for exporter feature
Exports rows from the grid in csv format, +the data exported is selected based on the provided options
rowTypes – {string} –
+which rows to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
Exports rows from the grid in pdf format, +the data exported is selected based on the provided options +Note that this function has a dependency on pdfMake, all +going well this has been installed for you. +The resulting pdf opens in a new browser window.
rowTypes – {string} –
+which rows to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
uiGridExporterConstants
+(constant in module ui.grid.exporter
+)
+constants available in exporter module
export all data, including data not visible. Can +be set for either rowTypes or colTypes
export all data, including data not visible. Can +be set only for rowTypes, selection of only some columns is +not supported
export only visible data, including data not visible. Can +be set for either rowTypes or colTypes
uiGridExporter
+(directive in module ui.grid.exporter
+)
+Adds exporter features to grid
<div ui-grid-exporter> + ... +</div>+
+
+This module provides the ability to exporter data from the grid.
+ +Data can be exported in a range of formats, and all data, visible +data, or selected rows can be exported, with all columns or visible +columns.
+ +No UI is provided, the caller should provide their own UI/buttons +as appropriate, or enable the gridMenu
+ +
+
uiGridExporterService
+(service in module ui.grid.exporter
+)
+Services for exporter feature
Determines the column widths base on the +widths we got from the grid. If the column is drawn +then we have a drawnWidth. If the column is not visible +then we have '*', 'x%' or a width. When columns are +not visible they don't contribute to the overall gridWidth, +so we need to adjust to allow for extra columns
+ +Our basic heuristic is to take the current gridWidth, plus +numeric columns and call this the base gridwidth.
+ +To that we add 100 for any '*' column, and x% of the base gridWidth +for any column that is a %
grid – {Grid} –
+the grid from which data should be exported
exportHeaders – {object} –
+array of header information
{object}
+– an array of header widths
Exports rows from the grid in csv format, +the data exported is selected based on the provided options
grid – {Grid} –
+the grid from which data should be exported
rowTypes – {string} –
+which rows to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
Triggers download of a csv file. Logic provided +by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
fileName – {string} –
+the filename we'd like our file to be +given
csvContent – {string} –
+the csv content that we'd like to +download as a file
Formats the column headers and data as a CSV, +and sends that data to the user
exportColumnHeaders – {array} –
+an array of column headers, +where each header is an object with name, width and maybe alignment
exportData – {array} –
+an array of rows, where each row is +an array of column data
{string}
+– csv the formatted csv as a string
Renders a single field as a csv field, including +quotes around the value
field – {field} –
+the field to be turned into a csv string, +may be of any type
{string}
+– a csv-ified version of the field
Renders a single field as a pdf-able field, which +is different from a csv field only in that strings don't have quotes +around them
field – {field} –
+the field to be turned into a pdf string, +may be of any type
{string}
+– a string-ified version of the field
Renders a single field as a csv field, including +quotes around the value
exporter – {exporterService} –
+pass in exporter
row – {array} –
+the row to be turned into a csv string
{string}
+– a csv-ified version of the row
Renders a row in a format consumable by PDF, +mainly meaning casting everything to a string
exporter – {exporterService} –
+pass in exporter
row – {array} –
+the row to be turned into a csv string
{string}
+– a csv-ified version of the row
Gets the column headers from the grid to use +as a title row for the exported file, all headers have +headerCellFilters applied as appropriate.
+ +Column headers are an array of objects, each object has +name, displayName, width and align attributes. Only name is +used for csv, all attributes are used for pdf.
grid – {Grid} –
+the grid from which data should be exported
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
Gets data from the grid based on the provided options,
+all cells have cellFilters applied as appropriate. Any rows marked
+exporterEnableExporting: false
will not be exported
grid – {Grid} –
+the grid from which data should be exported
rowTypes – {string} –
+which rows to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
Checks whether current browser is IE and returns it's version if it is
Exports rows from the grid in pdf format, +the data exported is selected based on the provided options. +Note that this function has a dependency on pdfMake, which must +be installed. The resulting pdf opens in a new +browser window.
grid – {Grid} –
+the grid from which data should be exported
rowTypes – {string} –
+which rows to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
colTypes – {string} –
+which columns to export, valid values are +uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE, +uiGridExporterConstants.SELECTED
Renders the data into a pdf, and opens that pdf.
grid – {Grid} –
+the grid from which data should be exported
exportColumnHeaders – {array} –
+an array of column headers, +where each header is an object with name, width and maybe alignment
exportData – {array} –
+an array of rows, where each row is +an array of column data
{object}
+– a pdfMake format document definition, ready +for generation
gridMenuService
+(service in module ui.grid
+)
+Methods for working with the grid menu
Sets up the gridMenu. Most importantly, sets our +scope onto the grid object as grid.gridMenuScope, allowing us +to operate when passed only the grid. Second most importantly, +we register the 'addToGridMenu' and 'removeFromGridMenu' methods +on the core api.
$scope – {$scope} –
+the scope of this gridMenu
grid – {Grid} –
+the grid to which this gridMenu is associated
Adds two menu items for each of the columns in columnDefs. One +menu item for hide, one menu item for show. Each is visible when appropriate +(show when column is not visible, hide when column is visible). Each toggles +the visible property on the columnDef using toggleColumnVisibility
$scope – {$scope} –
+of a gridMenu, which contains a reference to the grid
Toggles the visibility of an individual column. Expects to be +provided a context that has on it a gridColumn, which is the column that +we'll operate upon. We change the visibility, and refresh the grid as appropriate
gridCol – {GridCol} –
+the column that we want to toggle
i18nConstants
+(constant in module ui.grid.i18n
+)
+constants available in i18n module
+
+This module provides i18n functions to ui.grid and any application that wants to use it
+ +i18nService
+(service in module ui.grid.i18n
+)
+Services for i18n
Adds the languages and strings to the cache. Decorate this service to +add more translation strings
lang – {string} –
+language to add
stringMaps – {object} –
+of strings to add grouped by property names
+i18nService.add('en', { + aggregate: { + label1: 'items', + label2: 'some more items' + } + }, + groupPanel: { + description: 'Drag a column header here and drop it to group by that column.' + } +} +
return all currently loaded languages
lang – {string} –
+to return. If not specified, returns current language
{object}
+– the translation string maps for the language
return all currently loaded languages
{array}
+– string
returns the current language used in the application
returns the text specified in the path or a Missing text if text is not found
path – {string} –
+property path to use for retrieving text from string map
lang – {string} –
+to return. If not specified, returns current language
{object}
+– the translation for the path
+i18nService.getSafeText('sort.ascending') +
sets the current language to use in the application +$broadcasts the Update_Event on the $rootScope
lang – {string} –
+to set
+i18nService.setCurrentLang('fr'); +
GridOptions
+(api in module ui.grid.importer
+)
+GridOptions for importer feature, these are available to be
+set using the ui-grid gridOptions
A mandatory callback function that adds data to the source data array. The grid +generally doesn't add rows to the source data array, it is tidier to handle this through a user +callback.
+ ++ gridOptions.importerDataAddCallback: function( grid, newObjects ) { + $scope.myData = $scope.myData.concat( newObjects ); + }) +
grid – {Grid} –
+the grid we're importing into, may be useful in some way
newObjects – {array} –
+an array of new objects that you should add to your data
A callback function that provides custom error handling, rather +than the standard grid behaviour of an alert box and a console message. You +might use this to internationalise the console log messages, or to write to a +custom logging routine that returned errors to the server.
+ ++ gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) { + myUserDisplayRoutine( errorKey ); + myLoggingRoutine( consoleMessage, context ); + }) +
grid – {Grid} –
+the grid we're importing into, may be useful if you're positioning messages +in some way
errorKey – {string} –
+one of the i18n keys the importer can return - importer.noHeaders, +importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
consoleMessage – {string} –
+the English console message that importer would have written
context – {object} –
+the context data that importer would have appended to that console message, +often the file content itself or the element that is in error
A callback function that will filter (usually translate) a single +header. Used when you want to match the passed in column names to the column +displayName after the header filter.
+ +Your callback routine needs to return the filtered header value. +
+ gridOptions.importerHeaderFilter: function( displayName ) { + return $translate.instant( displayName ); + }) ++ +
or: +
+ gridOptions.importerHeaderFilter: $translate.instant +
displayName – {string} –
+the displayName that we'd like to translate
{string}
+– the translated name
A callback that massages the data for each object. For example, +you might have data stored as a code value, but display the decode. This callback +can be used to change the decoded value back into a code. Defaults to doing nothing.
grid – {Grid} –
+in case you need it
newObject – {object} –
+the new object as importer has created it, modify it +then return the modified version
{object}
+– the modified object
+gridOptions.importerObjectCallback = function ( grid, newObject ) { + switch newObject.status { + case 'Active': + newObject.status = 1; + break; + case 'Inactive': + newObject.status = 2; + break; + } + return newObject; +}; +
A callback function that will process headers using custom +logic. Set this callback function if the headers that your user will provide in their +import file don't necessarily match the grid header or field names. This might commonly +occur where your application is internationalised, and therefore the field names +that the user recognises are in a different language than the field names that +ui-grid knows about.
+ +Defaults to the internal processHeaders
method, which seeks to match using both
+displayName and column.name. Any non-matching columns are discarded.
Your callback routine should respond by processing the header array, and returning an array +of matching column names. A null value in any given position means "don't import this column"
+ ++ gridOptions.importerProcessHeaders: function( headerArray ) { + var myHeaderColumns = []; + var thisCol; + headerArray.forEach( function( value, index ) { + thisCol = mySpecialLookupFunction( value ); + myHeaderColumns.push( thisCol.name ); + }); + + return myHeaderCols; + }) +
grid – {Grid} –
+the grid we're importing into
headerArray – {array} –
+an array of the text from the first row of the csv file, +which you need to match to column.names
{array}
+– array of matching column names, in the same order as the headerArray
Whether or not importer is enabled. Automatically set +to false if the user's browser does not support the required fileApi. +Otherwise defaults to true.
An object on which we call new
to create each new row before inserting it into
+the data array. Typically this would be a $resource entity, which means that if you're using
+the rowEdit feature, you can directly call save on this entity when the save event is triggered.
Defaults to a vanilla javascript object
+gridOptions.importerNewObject = MyRes; +
PublicApi
+(api in module ui.grid.importer
+)
+Public Api for importer feature
Imports a file into the grid using the file object +provided. Bypasses the grid menu
fileObject – {File} –
+the file we want to import, as a javascript +File object
uiGridImporterConstants
+(constant in module ui.grid.importer
+)
+constants available in importer module
uiGridImporter
+(directive in module ui.grid.importer
+)
+Adds importer features to grid
<div ui-grid-importer> + ... +</div>+
uiGridImporterMenuItem
+(directive in module ui.grid.importer
+)
+Handles the processing from the importer menu item - once a file is +selected
<div ui-grid-importer-menu-item> + ... +</div>+
+
+This module provides the ability to import data into the grid. It +uses the column defs to work out which data belongs in which column, +and creates entities from a configured class (typically a $resource).
+ +If the rowEdit feature is enabled, it also calls save on those newly +created objects, and then displays any errors in the imported data.
+ +Currently the importer imports only CSV and json files, although provision has been +made to process other file formats, and these can be added over time.
+ +For json files, the properties within each object in the json must match the column names +(to put it another way, the importer doesn't process the json, it just copies the objects +within the json into a new instance of the specified object type)
+ +For CSV import, the default column identification relies on each column in the +header row matching a column.name or column.displayName. Optionally, a column identification +callback can be used. This allows matching using other attributes, which is particularly +useful if your application has internationalised column headings (i.e. the headings that +the user sees don't match the column names).
+ +The importer makes use of the grid menu as the UI for requesting an +import.
+ +uiGridImporterService
+(service in module ui.grid.importer
+)
+Services for importer feature
Inserts our new objects into the grid data, and +sets the rows to dirty if the rowEdit feature is being used
+ +Does this by registering a watch on dataChanges, which essentially +is waiting on the result of the grid data watch, and downstream processing.
+ +When the callback is called, it deregisters itself - we don't want to run +again next time data is added.
+ +If we never get called, we deregister on destroy.
grid – {Grid} –
+the grid we're importing into
newObjects – {array} –
+the objects we want to insert into the grid data
{object}
+– the new object
Provides an internationalised user alert for the failure,
+and logs a console message including diagnostic content.
+Optionally, if the the gridOptions.importerErrorCallback
routine
+is defined, then calls that instead, allowing user specified error routines
grid – {Grid} –
+the grid we're importing into
headerRow – {array} –
+the header row that we wish to match against +the column definitions
Converts an array of arrays (representing the csv file)
+into a set of objects. Uses the provided gridOptions.importerNewObject
+to create the objects, and maps the header row into the individual columns
+using either gridOptions.importerProcessHeaders
, or by using a native method
+of matching to either the displayName, column name or column field of
+the columns in the column defs. The resulting objects will have attributes
+that are named based on the column.field or column.name, in that order.
grid – {Grid} –
+the grid that we want to import into
importFile – {FileObject} –
+the file that we want to import, as a +file object
Creates a function that imports a csv file into the grid +(allowing it to be used in the reader.onload event)
grid – {Grid} –
+the grid that we want to import into
importFile – {FileObject} –
+the file that we want to import, as +a file object
Creates a function that imports a json file into the grid.
+The json data is imported into new objects of type gridOptions.importerNewObject
,
+and if the rowEdit feature is enabled the rows are marked as dirty
grid – {Grid} –
+the grid we want to import into
importFile – {FileObject} –
+the file that we want to import, as +a FileObject
Imports the provided file into the grid using the file object +provided. Bypasses the grid menu
grid – {Grid} –
+the grid we're importing into
fileObject – {File} –
+the file we want to import, as returned from the File +javascript object
Makes a new object based on gridOptions.importerNewObject
,
+or based on an empty object if not present
grid – {Grid} –
+the grid we're importing into
{object}
+– the new object
Parses a csv file into an array of arrays, with the first +array being the headers, and the remaining arrays being the data. +The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js, +which is noted as being under the MIT license. The code is modified to pass the jscs yoda condition +checker
importFile – {FileObject} –
+the file that we want to import, as a +file object
Parses a json file, returns the parsed data. +Displays an error if file doesn't parse
grid – {Grid} –
+the grid that we want to import into
importFile – {FileObject} –
+the file that we want to import, as +a FileObject
{array}
+– array of objects from the imported json
Determines the columns that the header row from +a csv (or other) file represents.
grid – {Grid} –
+the grid we're importing into
headerRow – {array} –
+the header row that we wish to match against +the column definitions
{array}
+– an array of the attribute names that should be used +for that column, based on matching the headers or creating the headers
GridOptions
+(api in module ui.grid.infiniteScroll
+)
+GridOptions for infinite scroll feature, these are available to be +set using the ui-grid gridOptions
Enable infinite scrolling for this grid
+
Defaults to true
PublicAPI
+(api in module ui.grid.infiniteScroll
+)
+Public API for infinite scroll feature
This function is used as a promise when data finished loading. +See infinite_scroll ngdoc for example of usage
This event fires when scroll reached bottom percentage of grid +and needs to load data
+ +This event fires when scroll reached top percentage of grid +and needs to load data
+ +uiGridInfiniteScroll
+(directive in module ui.grid.infiniteScroll
+)
+Adds infinite scroll features to grid
<div ui-grid-infinite-scroll> + ... +</div>+
+
+This module provides infinite scroll functionality to ui-grid
uiGridInfiniteScrollService
+(service in module ui.grid.infiniteScroll
+)
+Service for infinite scroll features
This function checks scroll position inside grid and +calls 'loadData' function when scroll reaches 'infiniteScrollPercentage'
This method register events and methods into grid public API
This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
ColumnDef
+(api in module ui.grid.moveColumns
+)
+Column Definition for move column feature, these are available to be +set using the ui-grid gridOptions.columnDefs
Enable column moving for the column.
GridOptions
+(api in module ui.grid.moveColumns
+)
+Options for configuring the move column feature, these are available to be +set using the ui-grid gridOptions
If defined, sets the default value for the colMovable flag on each individual colDefs +if their individual enableColumnMoving configuration is not defined. Defaults to true.
PublicApi
+(api in module ui.grid.moveColumns
+)
+Public Api for column moving feature.
Method can be used to change column position. +
+ gridApi.colMovable.moveColumn(oldPosition, newPosition) +
originalPosition – {integer} –
+of the column
finalPosition – {integer} –
+of the column
raised when column is moved +
+ gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){}) ++ +
colDef – {object} –
+the column that was moved
originalPosition – {integer} –
+of the column
finalPosition – {integer} –
+of the column
uiGridHeaderCell
+(directive in module ui.grid.moveColumns
+)
+Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
+ +On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid. + In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists). + On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
+ +Events that invoke cloning of header cell: + - mousedown
+ +Events that invoke movement of cloned header cell: + - mousemove
+ +Events that invoke repositioning of column: + - mouseup
<div ui-grid-header-cell> + ... +</div>+
uiGridMoveColumns
+(directive in module ui.grid.moveColumns
+)
+Adds column moving features to the ui-grid directive.
<div ui-grid-move-columns> + ... +</div>+
+
+This module provides column moving capability to ui.grid. It enables to change the position of columns.
+ +uiGridMoveColumnService
+(service in module ui.grid.moveColumns
+)
+Service for column moving feature.
GridOptions
+(api in module ui.grid.pagination
+)
+GridOptions for the pagination feature, these are available to be +set using the ui-grid gridOptions
Enables pagination, defaults to true
Enables the paginator at the bottom of the grid. Turn this off, if you want to implement your +own controls outside the grid.
Current page number, defaults to 1
Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
Array of page sizes, defaults to [250, 500, 1000]
A custom template for the pager, defaults to ui-grid/pagination
Total number of items, set automatically when client side pagination, needs set by user +for server side pagination
Disables client side pagination. When true, handle the paginationChanged event and set data
+and totalItems, defaults to false
PublicAPI
+(api in module ui.grid.pagination
+)
+Public API for the pagination feature
Returns the number of the current page
Returns the total number of pages
Moves to the next page, if possible
Moves to the previous page, if we're not on the first page
Moves to the requested page
page – {int} –
+The number of the page that should be displayed
This event fires when the pageSize or currentPage changes
+ +currentPage – {int} –
+requested page number
pageSize – {int} –
+requested page size
uiGridPager
+(directive in module ui.grid.pagination
+)
+Panel for handling pagination
<div ui-grid-pager> + ... +</div>+as class
<div class="ui-grid-pager"> + ... +</div>+
uiGridPagination
+(directive in module ui.grid.pagination
+)
+Adds pagination features to grid
<div ui-grid-pagination> + ... +</div>+
+
+This module provides pagination support to ui-grid
uiGridPaginationService
+(service in module ui.grid.pagination
+)
+Service for the pagination feature
Attaches the service to a certain grid
grid – {Grid} –
+The grid we want to work with
Raises paginationChanged and calls refresh for client side pagination
grid – {Grid} –
+the grid for which the pagination changed
currentPage – {int} –
+requested page number
pageSize – {int} –
+requested page size
PublicAPI
+(api in module ui.grid.paging
+)
+Public API for the paging feature
This event fires when the pageSize or currentPage changes
+ +requested – {currentPage} –
+page number
requested – {pageSize} –
+page size
uiGridPager
+(directive in module ui.grid.paging
+)
+Panel for handling paging
<div ui-grid-pager> + ... +</div>+as class
<div class="ui-grid-pager"> + ... +</div>+
uiGridPaging
+(directive in module ui.grid.paging
+)
+Adds paging features to grid
<div ui-grid-paging> + ... +</div>+
+
+This module provides paging support to ui-grid
uiGridPagingService
+(service in module ui.grid.paging
+)
+Service for the paging feature
Attaches the service to a certain grid
grid – {Grid} –
+The grid we want to work with
Raises pagingChanged and calls refresh for client side paging
the – {grid} –
+grid for which the paging changed
requested – {currentPage} –
+page number
requested – {pageSize} –
+page size
ColDef
+(api in module ui.grid.pinning
+)
+ColDef for pinning feature
ColumnDef
+(api in module ui.grid.pinning
+)
+ColumnDef for pinning feature, these are available to be +set using the ui-grid gridOptions.columnDefs
Enable pinning for the individual column.
+
Defaults to true
Column is pinned left when grid is rendered
+
Defaults to false
Column is pinned right when grid is rendered
+
Defaults to false
GridOptions
+(api in module ui.grid.pinning
+)
+GridOptions for pinning feature, these are available to be
+set using the ui-grid gridOptions
Enable pinning for the entire grid.
+
Defaults to true
+
+This module provides column pinning to the end user via menu options in the column header
+
+
ColDef
+(api in module ui.grid.resizeColumns
+)
+ColDef for resizeColumns feature
ColumnDef
+(api in module ui.grid.resizeColumns
+)
+ColumnDef for resizeColumns feature, these are available to be +set using the ui-grid gridOptions.columnDefs
Enable column resizing on an individual column
+
Defaults to GridOptions.enableColumnResizing
GridOptions
+(api in module ui.grid.resizeColumns
+)
+GridOptions for resizeColumns feature, these are available to be
+set using the ui-grid gridOptions
Enable column resizing on the entire grid
+
Defaults to true
PublicApi
+(api in module ui.grid.resizeColumns
+)
+Public Api for column resize feature.
raised when column is resized +
+ gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){}) ++ +
colDef – {object} –
+the column that was resized
delta – {integer} –
+of the column size change
uiGridColumnResizer
+(directive in module ui.grid.resizeColumns
+)
+Draggable handle that controls column resizing.
<div ui-grid-column-resizer> + ... +</div>+
uiGridResizeColumns
+(directive in module ui.grid.resizeColumns
+)
+Enables resizing for all columns on the grid. If, for some reason, you want to use the ui-grid-resize-columns directive, but not allow column resizing, you can explicitly set the +option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
<div ui-grid-resize-columns> + ... +</div>+
GridOptions
+(api in module ui.grid.rowEdit
+)
+Options for configuring the rowEdit feature, these are available to be
+set using the ui-grid gridOptions
How long the grid should wait for another change on this row +before triggering a save (in milliseconds). If set to -1, then saves are +never triggered by timer (implying that the user will call flushDirtyRows() +manually)
Setting the wait interval to 4 seconds +
+ $scope.gridOptions = { rowEditWaitInterval: 4000 } +
PublicApi
+(api in module ui.grid.rowEdit
+)
+Public Api for rowEdit feature
Triggers a save event for all currently dirty rows, could +be used where user presses a save button or navigates away from the page +
+ gridApi.rowEdit.flushDirtyRows(grid) +
{promise}
+– a promise that represents the aggregate of all +of the individual save promises - i.e. it will be resolved when all +the individual save promises have been resolved.
Returns all currently dirty rows +
+ gridApi.rowEdit.getDirtyRows(grid) +
{array}
+– An array of gridRows that are currently dirty
Returns all currently errored rows +
+ gridApi.rowEdit.getErrorRows(grid) +
{array}
+– An array of gridRows that are currently in error
Sets each of the rows passed in dataRows +to be dirty. note that if you have only just inserted the +rows into your data you will need to wait for a $digest cycle +before the gridRows are present - so often you would wrap this +call in a $interval or $timeout +
+ $interval( function() { + gridApi.rowEdit.setRowsDirty(grid, myDataRows); + }, 0, 1); +
dataRows – {array} –
+the data entities for which the gridRows +should be set dirty.
Sets the promise associated with the row save, mandatory that +the saveRow event handler calls this method somewhere before returning. +
+ gridApi.rowEdit.setSavePromise(grid, rowEntity) +
rowEntity – {object} –
+a data row from the grid for which a save has +been initiated
savePromise – {promise} –
+the promise that will be resolved when the +save is successful, or rejected if the save fails
raised when a row is ready for saving. Once your +row has saved you may need to use angular.extend to update the +data entity with any changed data from your save (for example, +lock version information if you're using optimistic locking, +or last update time/user information).
+ +Your method should call setSavePromise somewhere in the body before +returning control. The feature will then wait, with the gridRow greyed out +whilst this promise is being resolved.
+ ++ gridApi.rowEdit.on.saveRow(scope,function(rowEntity){}) ++and somewhere within the event handler: +
+ gridApi.rowEdit.setSavePromise( grid, rowEntity, savePromise) ++ +
rowEntity – {object} –
+the options.data element that was edited
uiGridRowEditConstants
+(constant in module ui.grid.rowEdit
+)
+constants available in row edit module
uiGridEdit
+(directive in module ui.grid.rowEdit
+)
+Adds row editing features to the ui-grid-edit directive.
<div ui-grid-edit> + ... +</div>+
uiGridViewport
+(directive in module ui.grid.rowEdit
+)
+Stacks on top of ui.grid.uiGridViewport to alter the attributes used +for the grid row to allow coloring of saving and error rows
<div ui-grid-viewport> + ... +</div>+as class
<div class="ui-grid-viewport"> + ... +</div>+
+
+This module extends the edit feature to provide tracking and saving of rows
+of data. The tutorial provides more information on how this feature is best
+used here.
+
+This feature depends on usage of the ui-grid-edit feature, and also benefits
+from use of ui-grid-cellNav to provide the full spreadsheet-like editing
+experience
uiGridRowEditService
+(service in module ui.grid.rowEdit
+)
+Services for row editing features
Receives a beginCellEdit event from the edit function, +and cancels any rowEditSaveTimers if present, as the user is still editing +this row. Only the rowEntity parameter +is processed, although other params are available. Grid +is automatically provided by the gridApi.
rowEntity – {object} –
+the data entity for which the cell +editing has commenced
Receives a cancelCellEdit event from the edit function, +and if the row was already dirty, restarts the save timer. If the row +was not already dirty, then it's not dirty now either and does nothing.
+ +Only the rowEntity parameter +is processed, although other params are available. Grid +is automatically provided by the gridApi.
rowEntity – {object} –
+the data entity for which the cell +editing was cancelled
cancel the $interval for any timer running on this row +then delete the timer itself
grid – {object} –
+the grid for which we are processing
gridRow – {GridRow} –
+the row for which the timer should be adjusted
Consider setting a timer on this row (if it is dirty). if there is a timer running +on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is +dirty and not currently saving then set a new timer
grid – {object} –
+the grid for which we are processing
gridRow – {GridRow} –
+the row for which the timer should be adjusted
Receives an afterCellEdit event from the edit function, +and sets flags as appropriate. Only the rowEntity parameter +is processed, although other params are available. Grid +is automatically provided by the gridApi.
rowEntity – {object} –
+the data entity for which the cell +was edited
Triggers a save event for all currently dirty rows, could +be used where user presses a save button or navigates away from the page +
+ gridApi.rowEdit.flushDirtyRows(grid) +
grid – {object} –
+the grid for which dirty rows should be flushed
{promise}
+– a promise that represents the aggregate of all +of the individual save promises - i.e. it will be resolved when all +the individual save promises have been resolved.
Checks whether a row is already present +in the given array
rowArray – {array} –
+the array in which to look for the row
gridRow – {GridRow} –
+the row that should be looked for
Returns a function that processes the failed +resolution of a save promise
grid – {object} –
+the grid for which the promise should be processed
gridRow – {GridRow} –
+the row that is now in error
{function}
+– the error handling function
Returns a function that processes the successful +resolution of a save promise
grid – {object} –
+the grid for which the promise should be processed
gridRow – {GridRow} –
+the row that has been saved
{function}
+– the success handling function
Removes a row from a cache of rows - either +grid.rowEdit.errorRows or grid.rowEdit.dirtyRows. If the row +is not present silently does nothing.
rowArray – {array} –
+the array from which to remove the row
gridRow – {GridRow} –
+the row that should be removed
Returns a function that saves the specified row from the grid, +and returns a promise
grid – {object} –
+the grid for which dirty rows should be flushed
gridRow – {GridRow} –
+the row that should be saved
{function}
+– the saveRow function returns a function. That function +in turn, when called, returns a promise relating to the save callback
Sets each of the rows passed in dataRows +to be dirty. note that if you have only just inserted the +rows into your data you will need to wait for a $digest cycle +before the gridRows are present - so often you would wrap this +call in a $interval or $timeout +
+ $interval( function() { + gridApi.rowEdit.setRowsDirty( myDataRows); + }, 0, 1); +
grid – {object} –
+the grid for which rows should be set dirty
dataRows – {array} –
+the data entities for which the gridRows +should be set dirty.
Sets the promise associated with the row save, mandatory that +the saveRow event handler calls this method somewhere before returning. +
+ gridApi.rowEdit.setSavePromise(grid, rowEntity) +
grid – {object} –
+the grid for which dirty rows should be returned
rowEntity – {object} –
+a data row from the grid for which a save has +been initiated
savePromise – {promise} –
+the promise that will be resolved when the +save is successful, or rejected if the save fails
GridOptions
+(api in module ui.grid.saveState
+)
+GridOptions for saveState feature, these are available to be
+set using the ui-grid gridOptions
Save the current filter state for each column
+ +
Defaults to true
Save the current focused cell. On returning +to this focused cell we'll also scroll. This option is +preferred to the saveScroll option, so is set to true by +default. If saveScroll is set to true then this option will +be disabled.
+ +By default this option saves the current row number and column +number, and returns to that row and column. However, if you define +a saveRowIdentity function, then it will return you to the currently +selected column within that row (in a business sense - so if some +rows have been deleted, it will still find the same data, presuming it +still exists in the list. If it isn't in the list then it will instead +return to the same row number - i.e. scroll percentage)
+ +Note that this option will do nothing if the cellNav +feature is not enabled.
+ +
Defaults to true (unless saveScroll is true)
Save the current column order. Note that unless
+you've provided the user with some way to reorder their columns (for
+example the move columns feature), this makes little sense.
+
Defaults to true
A function that can be called, passing in a rowEntity,
+and that will return a unique id for that row. This might simply
+return the id
field from that row (if you have one), or it might
+concatenate some fields within the row to make a unique value.
This value will be used to find the same row again and set the focus +to it, if it exists when we return.
+ +
Defaults to undefined
Save the current scroll position. Note that this +is saved as the percentage of the grid scrolled - so if your +user returns to a grid with a significantly different number of +rows (perhaps some data has been deleted) then the scroll won't +actually show the same rows as before. If you want to scroll to +a specific row then you should instead use the saveFocus option, which +is the default.
+ +Note that this element will only be saved if the cellNav feature is
+enabled
+
Defaults to false
Save the currently selected rows. If the saveRowIdentity
callback
+is defined, then it will save the id of the row and select that. If not, then
+it will attempt to select the rows by row number, which will give the wrong results
+if the data set has changed in the mean-time.
Note that this option only does anything +if the selection feature is enabled.
+ +
Defaults to true
Save the current sort state for each column
+ +
Defaults to true
Save whether or not columns are visible.
+ +
Defaults to true
Save the current column widths. Note that unless
+you've provided the user with some way to resize their columns (say
+the resize columns feature), then this makes little sense.
+
Defaults to true
PublicApi
+(api in module ui.grid.saveState
+)
+Public Api for saveState feature
Restores the provided state into the grid
$scope – {scope} –
+a scope that we can broadcast on
state – {object} –
+the state that should be restored into the grid
Packages the current state of the grid into +an object, and provides it to the user for saving
{object}
+– the state as a javascript object that can be saved
uiGridSaveStateConstants
+(constant in module ui.grid.saveState
+)
+constants available in save state module
uiGridSaveState
+(directive in module ui.grid.saveState
+)
+Adds saveState features to grid
<div ui-grid-save-state> + ... +</div>+
+
+This module provides the ability to save the grid state, and restore +it when the user returns to the page.
+ +No UI is provided, the caller should provide their own UI/buttons +as appropriate. Usually the navigate events would be used to save +the grid state and restore it.
+ +
+
uiGridSaveStateService
+(service in module ui.grid.saveState
+)
+Services for saveState feature
Finds a row given it's identity value, returns the first found row +if any are found, otherwise returns null if no rows are found.
grid – {Grid} –
+the grid whose state we'd like to restore
rowVal – {object} –
+the row we'd like to find
{gridRow}
+– the found row, or null if none found
Helper function that gets either the rowNum or +the saveRowIdentity, given a gridRow
grid – {Grid} –
+the grid the row is in
gridRow – {GridRow} –
+the row we want the rowNum for
{object}
+– an object containing { identity: true/false, row: rowNumber/rowIdentity }
Applies the provided state to the grid
grid – {Grid} –
+the grid whose state we'd like to restore
$scope – {scope} –
+a scope that we can broadcast on
state – {object} –
+the state we'd like to restore
Restores the columns, including order, visible, width +sort and filters.
grid – {Grid} –
+the grid whose state we'd like to restore
columnsState – {object} –
+the list of columns we had before, with their state
Scrolls to the position that was saved. If focus is true, then +sets focus to the specified row/col. If focus is false, then scrolls to the +specified row/col.
grid – {Grid} –
+the grid whose state we'd like to restore
$scope – {scope} –
+a scope that we can broadcast on
scrollFocusState – {object} –
+the scroll/focus state ready to be restored
Selects the rows that are provided in the selection
+state. If you are using saveRowIdentity
and more than one row matches the identity
+function then only the first is selected.
grid – {Grid} –
+the grid whose state we'd like to restore
selectionState – {object} –
+the selection state ready to be restored
Saves the current grid state into an object, and +passes that object back to the caller
grid – {Grid} –
+the grid whose state we'd like to save
{object}
+– the state ready to be saved
Saves the column setup, including sort, filters, ordering +and column widths.
+ +Works through the current columns, storing them in order. Stores the +column name, then the visible flag, width, sort and filters for each column.
grid – {Grid} –
+the grid whose state we'd like to save
{array}
+– the columns state ready to be saved
Saves the currently scroll or focus.
+ +If cellNav isn't present then does nothing - we can't return +to the scroll position without cellNav anyway.
+ +If the cellNav module is present, and saveFocus is true, then +it saves the currently focused cell. If rowIdentity is present +then saves using rowIdentity, otherwise saves visibleRowNum.
+ +If the cellNav module is not present, and saveScroll is true, then +it approximates the current scroll row and column, and saves that.
grid – {Grid} –
+the grid whose state we'd like to save
{object}
+– the selection state ready to be saved
Saves the currently selected rows, if the selection feature is enabled
grid – {Grid} –
+the grid whose state we'd like to save
{object}
+– the selection state ready to be saved
GridOptions
+(api in module ui.grid.selection
+)
+GridOptions for selection feature, these are available to be +set using the ui-grid gridOptions
Enable a row header to be used for selection
+
Defaults to true
Enable row selection for entire grid.
+
Defaults to true
Enable the select all checkbox at the top of the selectionRowHeader
+
Defaults to true
If selected rows are changed in bulk, either via the API or
+via the selectAll checkbox, then a separate event is fired. Setting this
+option to false will cause the rowSelectionChanged event to be called multiple times
+instead
+
Defaults to true
Makes it possible to specify a method that evaluates for each and sets its "enableSelection" property.
Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
+
Defaults to false
Enable multiple row selection for entire grid
+
Defaults to true
Prevent a row from being unselected. Works in conjunction
+with multiselect = false
and gridApi.selection.selectRow()
to allow
+you to create a single selection only grid - a row is always selected, you
+can only select different rows, you can't unselect the row.
+
Defaults to false
can be used to set a custom width for the row header selection column
+
Defaults to 30px
GridRow
+(api in module ui.grid.selection
+)
+GridRow prototype functions added for selection
Sets the isSelected property and updates the selectedCount +Changes to isSelected state should only be made via this function
selelected – {bool} –
+value to set
Enable row selection for this row, only settable by internal code.
+ +The grouping feature, for example, might set group header rows to not be selectable.
+
Defaults to true
Selected state of row. Should be readonly. Make any changes to selected state using setSelected().
+
Defaults to false
PublicApi
+(api in module ui.grid.selection
+)
+Public Api for selection feature
Unselects all rows
event – {Event} –
+object if raised from an event
Returns whether or not the selectAll checkbox is currently ticked. The +grid doesn't automatically select rows when you add extra data - so when you add data +you need to explicitly check whether the selectAll is set, and then call setVisible rows +if it is
returns all selectedRow's as gridRows
returns all selectedRow's entity references
Selects all rows. Does nothing if multiSelect = false
event – {Event} –
+object if raised from an event
Selects all visible rows. Does nothing if multiSelect = false
event – {Event} –
+object if raised from an event
Select the data row
rowEntity – {object} –
+gridOptions.data[] array instance
event – {Event} –
+object if raised from an event
Select the specified row by visible index (i.e. if you +specify row 0 you'll get the first visible row selected). In this context +visible means of those rows that are theoretically visible (i.e. not filtered), +rather than rows currently rendered on the screen.
index – {number} –
+index within the rowsVisible array
event – {Event} –
+object if raised from an event
Sets the current gridOption.modifierKeysToMultiSelect to true or false
modifierKeysToMultiSelect – {bool} –
+true to only allow multiple rows when using ctrlKey or shiftKey is used
Sets the current gridOption.multiSelect to true or false
multiSelect – {bool} –
+true to allow multiple rows
Toggles data row as selected or unselected
rowEntity – {object} –
+gridOptions.data[] array instance
event – {Event} –
+object if raised from an event
UnSelect the data row
rowEntity – {object} –
+gridOptions.data[] array instance
event – {Event} –
+object if raised from an event
is raised after the row.isSelected state is changed
+ +row – {GridRow} –
+the row that was selected/deselected
event – {Event} –
+object if raised from an event
is raised after the row.isSelected state is changed
+in bulk, if the enableSelectionBatchEvent
option is set to true
+(which it is by default). This allows more efficient processing
+of bulk events.
rows – {array} –
+the rows that were selected/deselected
event – {Event} –
+object if raised from an event
uiGridSelectionConstants
+(constant in module ui.grid.selection
+)
+constants available in selection module
uiGridCell
+(directive in module ui.grid.selection
+)
+Stacks on top of ui.grid.uiGridCell to provide selection feature
<div ui-grid-cell> + ... +</div>+
uiGridSelection
+(directive in module ui.grid.selection
+)
+Adds selection features to grid
<div ui-grid-selection> + ... +</div>+
uiGridViewport
+(directive in module ui.grid.selection
+)
+Stacks on top of ui.grid.uiGridViewport to alter the attributes used +for the grid row
<div ui-grid-viewport> + ... +</div>+as class
<div class="ui-grid-viewport"> + ... +</div>+
selection
+(grid in module ui.grid.selection
+)
+Grid properties and functions added for selection
Current count of selected rows
var count = grid.selection.selectedCount
+
+This module provides row selection
+
+
uiGridSelectionService
+(service in module ui.grid.selection
+)
+Services for selection features
Clears all selected rows
grid – {Grid} –
+grid object
event – {Event} –
+object if raised from an event
Decides whether to raise a single event or a batch event
grid – {Grid} –
+grid object
row – {GridRow} –
+row that has changed
changedRows – {array} –
+an array to which we can append the changed
event – {Event} –
+object if raised from an event +row if we're doing batch events
Returns all the selected rows
grid – {Grid} –
+grid object
Decides whether we need to raise a batch event, and +raises it if we do.
grid – {Grid} –
+grid object
changedRows – {array} –
+an array of changed rows, only populated
event – {Event} –
+object if raised from an event +if we're doing batch events
selects a group of rows from the last selected row using the shift key
grid – {Grid} –
+grid object
clicked – {GridRow} –
+row
event – {Event} –
+object if raised from an event
multiSelect – {bool} –
+if false, does nothing this is for multiSelect only
Toggles row as selected or unselected
grid – {Grid} –
+grid object
row – {GridRow} –
+row to select or deselect
event – {Event} –
+object if resulting from event
multiSelect – {bool} –
+if false, only one row at time can be selected
noUnselect – {bool} –
+if true then rows cannot be unselected
GridUtil
+(service in module ui.grid
+)
+Grid utility functions
Binds given method to given object.
+ +By means of a wrapper, ensures that method
is always bound to
+object
regardless of its calling environment.
+Iow, inside method
, this
always points to object
.
See http://alistapart.com/article/getoutbindingsituations
Object – {object} –
+to bind 'this' to
Method – {method} –
+to bind
{Function}
+– The wrapper that performs the binding
Copied from https://github.com/shahata/angular-debounce +Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
func – {function} –
+function to debounce
wait – {number} –
+milliseconds to delay
immediate – {bool} –
+execute before delay
{function}
+– A function that can be executed as debounced function
+var debouncedFunc = gridUtil.debounce(function(){alert('debounced');}, 500); +debouncedFunc(); +debouncedFunc(); +debouncedFunc(); +
element – {element} –
+DOM element
[extra] – {string} –
+Optional modifier for calculation. Use 'margin' to account for margins on element
{number}
+– Element height in pixels, accounting for any borders, etc.
element – {element} –
+DOM element
[extra] – {string} –
+Optional modifier for calculation. Use 'margin' to account for margins on element
{number}
+– Element width in pixels, accounting for any borders, etc.
Return a list of column names, given a data set
data – {string} –
+Data array for grid
{Object}
+– Column definitions with field accessor and column name
+var data = [ + { firstName: 'Bob', lastName: 'Jones' }, + { firstName: 'Frank', lastName: 'Smith' } +]; + +var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties); + +columnDefs == [ + { + field: 'firstName', + name: 'First Name' + }, + { + field: 'lastName', + name: 'Last Name' + } +]; +
Get's template from cache / element / url
Either – {string|element|promise} –
+a string representing the template id, a string representing the template url, +an jQuery/Angualr element, or a promise that returns the template contents to use.
{object}
+– a promise resolving to template contents
+GridUtil.getTemplate(url).then(function (contents) { + alert(contents); + }) +
guesses the type of an argument
item – {string/number/bool/object} –
+variable to examine
{string}
+– one of the following +'string' +'boolean' +'number' +'date' +'object'
wraps the $log method, allowing us to choose different +treatment within ui-grid if we so desired. At present we only log +debug messages if uiGridConstants.LOGDEBUGMESSAGES is set to true
wraps the $log method, allowing us to choose different +treatment within ui-grid if we so desired. At present we only log +error messages if uiGridConstants.LOGERRORMESSAGES is set to true
logMessage – {string} –
+message to be logged to the console
wraps the $log method, allowing us to choose different +treatment within ui-grid if we so desired. At present we only log +warning messages if uiGridConstants.LOGWARNMESSAGES is set to true
logMessage – {string} –
+message to be logged to the console
Return a unique ID string
{string}
+– Unique string
+var id = GridUtil.newId(); + +# 1387305700482; +
Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
element – {element} –
+The element to get the scrollLeft
from.
{int}
+– A normalized scrollLeft value for the current browser.
Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
element – {element} –
+The element to normalize the scrollLeft
value for
scrollLeft – {int} –
+The scrollLeft
value to denormalize.
{int}
+– A normalized scrollLeft value for the current browser.
Given an event from this list:
+ +wheel, mousewheel, DomMouseScroll, MozMousePixelScroll
"normalize" it +so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
event – {event} –
+A mouse wheel event
{event}
+– A normalized event
Takes a field path and converts it to bracket notation to allow for special characters in path
path – {string} –
+Path to evaluate
{string}
+– A path that is normalized.
+gridUtil.preEval('property') == 'property' +gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']" +
columnName – {string} –
+Column name as a string
{string}
+– Column name appropriately capitalized and split apart
Adapted from debounce function (above) +Potential keys for Params Object are: + trailing (bool) - whether to trigger after throttle time ends if called multiple times
func – {function} –
+function to throttle
wait – {number} –
+milliseconds to delay after first trigger
params – {Object} –
+to use in throttle.
{function}
+– A function that can be executed as throttled function
+var throttledFunc = gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true}); +throttledFunc(); //=> logs throttled +throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy) +throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else. +
gridClassFactory
+(service in module ui.grid
+)
+factory to return dom specific instances of a grid
Creates a new grid instance. Each instance will have a unique id
options – {object} –
+An object map of options to pass into the created grid instance.
{Grid}
+– grid
Processes designTime column definitions and applies them to col for the +core grid features
colDef – {object} –
+reference to column definition
col – {GridColumn} –
+reference to gridCol
gridOptions – {object} –
+reference to grid options
rowSearcher
+(service in module ui.grid
+)
+Service for searching/filtering rows based on column value conditions.
Get the term from a filter +Trims leading and trailing whitespace
filter – {object} –
+object to use
{object}
+– Parsed term
Guess the condition for a filter based on its term
+
+Defaults to STARTSWITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
+Uses STARTSWITH for strings ending with * (bo). Uses ENDS_WITH for strings starting with * (ob).
filter – {object} –
+object to use
{uiGridConstants.filter<int>}
+– Value representing the condition constant value
Runs a single pre-parsed filter against a cell, returning true +if the cell matches that one filter.
grid – {Grid} –
+the grid we're working against
row – {GridRow} –
+the row we're matching against
column – {GridCol} –
+the column that we're working against
filter – {object} –
+the specific, preparsed, filter that we want to test
{boolean}
+– true if we match (row stays visible)
Run a search across the given rows and columns, marking any rows that don't +match the stored col.filters or col.filter as invisible.
grid – {Grid} –
+Grid instance to search inside
rows – {Array[GridRow]} –
+GridRows to filter
columns – {Array[GridColumn]} –
+GridColumns with filters to process
Process provided filters on provided column against a given row. If the row meets +the conditions on all the filters, return true.
grid – {Grid} –
+Grid to search in
row – {GridRow} –
+Row to search on
column – {GridCol} –
+Column with the filters to use
filters – {array} –
+array of pre-parsed/preprocessed filters to apply
{boolean}
+– Whether the column matches or not.
For a given columns filters (either col.filters, or [col.filter] can be passed in), +do all the parsing and pre-processing and store that data into a new filters object. The object +has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
+ +We could use a forEach in here, since it's much less performance sensitive, but since we're using +for loops everywhere else in this module...
filters – {array} –
+the filters from the column (col.filters or [col.filter])
{array}
+– An array of parsed/preprocessed filters
Remove leading and trailing asterisk (*) from the filter's term
filter – {object} –
+object to use
{uiGridConstants.filter<int>}
+– Value representing the condition constant value
uiGridColumnMenuService
+(service in module ui.grid
+)
+Services for working with column menus, factored out +to make the code easier to understand
gets the position information needed to place the column +menu below the column header
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
column – {GridCol} –
+the column we want to position below
$columnElement – {element} –
+the column element we want to position below
{hash}
+– containing left, top, offset, height, width
determines whether a column can be hidden, by checking the enableHiding columnDef option
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
Sets defaults, puts a reference to the $scope on +the uiGridController
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
uiGridCtrl – {controller} –
+the uiGridController for the grid +we're on
determines whether the requested sort direction is current active, to +allow highlighting in the menu
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
direction – {string} –
+the direction that we'd have selected for us to be active
determines whether this column is sortable
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
determines whether we should suppress the removeSort option
$scope – {$scope} –
+the $scope from the uiGridColumnMenu
uiGridStyle
+(directive in module ui.grid.style
+)
+Allows us to interpolate expressions in <style>
elements. Angular doesn't do this by default as it can/will/might? break in IE8.
<style ui-grid-style> + ... +</style>+
GridUtil
+(service in module ui.grid.util
+)
+Grid utility functions
element – {element} –
+DOM element
[extra] – {string} –
+Optional modifier for calculation. Use 'margin' to account for margins on element
{number}
+– Element height in pixels, accounting for any borders, etc.
element – {element} –
+DOM element
[extra] – {string} –
+Optional modifier for calculation. Use 'margin' to account for margins on element
{number}
+– Element width in pixels, accounting for any borders, etc.
Return a list of column names, given a data set
data – {string} –
+Data array for grid
{Object}
+– Column definitions with field accessor and column name
+var data = [ + { firstName: 'Bob', lastName: 'Jones' }, + { firstName: 'Frank', lastName: 'Smith' } +]; + +var columnDefs = GridUtil.getColumnsFromData(data); + +columnDefs == [ + { + field: 'firstName', + name: 'First Name' + }, + { + field: 'lastName', + name: 'Last Name' + } +]; +
Get's template from Url
{object}
+– a promise resolving to template contents
+GridUtil.getTemplate(url).then(function (contents) { + alert(contents); + }) +
Return a unique ID string
{string}
+– Unique string
+var id = GridUtil.newId(); + +# 1387305700482; +
Given an event from this list:
+ +wheel, mousewheel, DomMouseScroll, MozMousePixelScroll
"normalize" it +so that it stays consistent no matter what browser it comes from (i.e. scale it correctl and make sure the direction is right.)
event – {event} –
+A mouse wheel event
{event}
+– A normalized event
columnName – {string} –
+Column name as a string
{string}
+– Column name appropriately capitalized and split apart
+
+Ui-Grid 3.0 (formerly ng-grid), is a 100% angular grid written with no dependencies other than AngularJS. It is designed around +a core grid module and features are layered on as angular modules and directives. This keeps the core small and focused +while executing very complex features only when you need them.
+ +In the core module, you get:
+ +In this example we create the most basic grid possible.
+ +Steps:
+ ++ <link rel="styleSheet" href="release/ui-grid-unstable.css"/> + <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script> + <script src="/release/ui-grid-unstable.js"></script> ++
+ var app = angular.module('app', ['ui.grid']); ++
+ .myGrid { + width: 500px; + height: 250px; + } ++
+ $scope.myData = [ + { + "firstName": "Cox", + "lastName": "Carney"... ++
+ <div ng-controller="MainCtrl"> + <div ui-grid="{ data: myData }" class="myGrid"></div> + </div> ++ +
+
+The ui-grid team is reasonably small, and this is a large project. We are always looking for +contributors to assist to make this the default grid for all angularJS projects.
+ +There are a number of ways you can contribute, all of which add value to the team:
+ +We are a friendly project that welcomes new members, and we have tried to provide guides that +allow both open source pros and first timers to open source to contribute to the project.
+ +Refer to CONTRIBUTING.md, +in the issue tracker and bug report sections.
+ +In short, the issue tracker should be used for demonstrable problems in the code. You should +tell us what version you're using, what the problem is that you have, what you expected to happen +instead, and if possible provide a plunkr that demonstrates the problem.
+ +The easiest way to get a plunkr is to start with one of the tutorials, click the "edit" button, +and then adjust it in the plunkr to match the configuration in your code. Then click the save +button, and copy and paste the url into your issue.
+ +There is a lot more detail in CONTRIBUTING.md, +read it and follow it.
+ +As you get more familiar with ui-grid, you'll find that some things in the documentation aren't +described as well as you think they could be, or you'll start to build knowledge that is enough +to answer questions.
+ +Register for gitter, and get a github account. Answer questions where you have the knowledge to +do so, freeing up the core team to work on the more complex issues. Remember that you don't have to +always be right, this is an open source project run by volunteers. So long as you have good intentions +and your courteous, then your contributions will be valued. The only way to increase your knowledge is +to start, and we all started in the same place.
+ +If you want to contribute documentation updates, these are the easiest area of pull requests to submit. +Take a look at the first timers guide, +and do your first pull request. It's not as hard as it looks from the outside!!
+ +If you're already a pro with open source, github and the development tools, dive straight in. Look +at CONTRIBUTING.md and +DEVELOPER.md, and get working!!
+ +Key things to watch out for are:
+ +If you're more of a first timer with open source, then look at First time guide. +Some of it you'll already know, other bits may be new. Our aim with this guide is to collate as much as possible of the +questions and answers that we've covered through gitter and other channels, and make that information +accessible to new contributors.
+
+ui-grid 3.0 is a substantial upgrade from ng-grid 2.x, with the majority of the code base having +been rewritten. Where possible the configuration is backward compatible, but some concepts have +changed in ways that require code change to integrate.
+ +This tutorial covers the key elements that may require adjusting in your application.
+ +Previously you included the grid within your application using: +
+ angular.module( 'yourApplication', [ + 'ngGrid' + ]); ++ +
You now include ui.grid instead, and may optionally also include the modules for features +that you choose to enable: +
+ angular.module( 'yourApplication', [ + 'ui.grid', + 'ui.grid.edit' + ]); ++ +
Similarly, the directive name has changed, and you may choose to include additional features +within your grid.
+ +Previously you had: +
+ <div class="gridStyle" ng-grid="gridOptions"></div> ++ +
You now include multiple directives for each of the features you wish to use: +
+ <div class="gridStyle" ui-grid="gridOptions" ui-grid-edit></div> ++ +
All columns must have a name or a field. If you have columns that have neither +you need to define one. Name will be derived from field if not present. +
+$scope.gridOptions = { + columnDefs: [ + {field: 'id', displayName: 'Id'}, + {field: 'name', displayName: 'Name'}, + {displayName: 'Edit', cellTemplate: '<button id="editBtn" type="button" class="btn-small" >Edit</button> '} + ] + }; ++ +
Becomes: +
+$scope.gridOptions = { + columnDefs: [ + {field: 'id', displayName: 'Id'}, + {field: 'name', displayName: 'Name'}, + {name: 'edit', displayName: 'Edit', cellTemplate: '<button id="editBtn" type="button" class="btn-small" ng-click="edit(row.entity)" >Edit</button> '} + ] + }; ++ +
String values are no longer supported for columns defs: +
+ $scope.myColDefs = {[...]}; + $scope.gridOptions.columnDefs = 'myColDefs' ++ +
+$scope.gridOptions.columnDefs = $scope.myColDefs = [...]; ++or +
+$scope.gridOptions.columnDefs = [...]; ++ +
In 2.x you would use row.getProperty(col.field)
within a cellTemplate to get the value of a cell. In 3.0 this has changed to grid.getCellValue(row, col)
.
The grid now uses an isolate scope, meaning that the scope on your controller is not directly accessible +to widgets that you include in the grid. You can get the parent scope used by the ui-grid element in any template +with the grid.appScope property. {{grid.appScope}}
+ ++$scope.gridOptions = { + columnDefs: [ + {field: 'id', displayName: 'Id'}, + {field: 'name', displayName: 'Name'}, + {name: 'edit', displayName: 'Edit', cellTemplate: '<button id="editBtn" type="button" class="btn-small" ng-click="edit(row.entity)" >Edit</button> '} + ] + }; ++ +
becomes: +
+$scope.gridOptions = { + columnDefs: [ + {field: 'id', displayName: 'Id'}, + {field: 'name', displayName: 'Name'}, + {name: 'edit', displayName: 'Edit', cellTemplate: '<button id="editBtn" type="button" class="btn-small" ng-click="grid.appScope.edit(row.entity)" >Edit</button> '} + ] + }; ++ +
Many elements included by default in ng-grid have now been shifted into separate features, allowing the +core ng-grid to be kept smaller and faster. Features are enabled only when included, inclusion of a feature +requires both including the module in your application and adding the feature directive onto the grid +definition.
+ +Features include:
+ +For example, to include the selection feature, you would include the module: +
+ angular.module( 'yourApplication', [ + 'ui.grid', + 'ui.grid.selection' + ]); ++ +
and include the relevant directive on the grid that you wish to have access to the feature: +
+ <div class="gridStyle" ui-grid="gridOptions" ui-grid-edit ui-grid-selection></div> ++ +
The filtering api changes substantially, as filters are now per-column rather than for the grid as +a whole. Refer the filtering documentation, the key change is that filters are now stored on the +individual columns rather than as a single filterOptions element.
+ +Sorting behaviour changes somewhat, and again sort options are moved onto the individual columns, +along with provision of a "priority" element within the sortOptions.
+
+Current list of tested browsers: Sauce Labs
+ +
+
+UI-Grid 3.0 (formerly ng-grid), is a 100% angular grid written with no dependencies other than AngularJS. It is designed around +a core grid module and features are layered on as angular modules and directives. This keeps the core small and focused +while executing very complex features only when you need them.
+ +In the core module, you get:
+ +In this example we create the most basic grid possible.
+ +Steps:
+ ++ <link rel="styleSheet" href="release/ui-grid-unstable.css"/> + <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script> + <script src="/release/ui-grid-unstable.js"></script> ++
+ var app = angular.module('app', ['ui.grid']); ++
+ .myGrid { + width: 500px; + height: 250px; + } ++
+ $scope.myData = [ + { + "firstName": "Cox", + "lastName": "Carney"... ++
+ <div ng-controller="MainCtrl"> + <div ui-grid="{ data: myData }" class="myGrid"></div> + </div> ++ +
+
+UI-Grid allows you to sort rows. The feature is on by default. You can set the enableSorting
flag in your grid options to enable/disable it.
+ Note: You can sort columns by accessing the column menu. You must include ngAnimate in your application if you want the menu to slide up/down, but it's not required. +
+ +Sorting can be disabled at the column level by setting enableSorting: false
in the column def. See the last column below for an example.
Multiple columns can be sorted by shift-clicking on the 2-n columns. To see it in action, sort Gender then shift-click Name.
+ +When sorting using the menus, the sorts are additive. So if you have one column sorted and you pick another sort +from a column menu, it will add on to the existing sort rather than replacing it. You need to use the 'remove sort' option +on the existing column if you don't want to sort by it any more.
+ +When sorting using the headers, clicking on a header removes all other sorts unless you shift-click.
+ +The sort is automatically recalculated when you edit a field (the edit feature calls the dataChange api to notify of a data change). If
+you change the data "behind the scenes" and want the sort to be recalculated, you can notify the grid that your
+data has changed by calling gridApi.core.notifyDataChange( uiGridConstants.dataChange.EDIT )
If you set a default sort, you can prevent the user from removing that sort by setting suppressRemoveSort: true
+for that column. This will let the user change the direction of the sort, but take away the option to remove the sort.
+
+UI-Grid allows you to filter rows. Just set the enableFiltering
flag in your grid options (it is off by default).
Filtering can be disabled at the column level by setting enableFiltering: false
in the column def. See the "company" column below for an example.
The filter field can be pre-populated by setting filter: { term: 'xxx' }
in the column def. See the "gender" column below.
The filter
object introduced above may also specify a condition
, which defines how rows are chosen as matching the filter term. UI-Grid comes with several conditions
+out-of-the-box, which are defined by uiGridConstants.filter.*
. See the "email" column below.
If no condition is set, UI-Grid will take a best guess based on the contents of the filter field. Even basic wildcard (*) use is supported!
+ +If you want to create your own filtering logic, the condition
field of the filter
object can also be a function that gets run for each
+row. Such a function has the following signature:
function myCustomSorter(searchTerm, cellValue, row, column) {
+ // Custom logic that returns true if `row`
+ // passes the filter and false if it should
+ // be filtered out
+ return booleanResult;
+}
+
+
+For an example of this, see the "phone" column below for an example of this; the custom filter condition makes sure to strip the phone number of everything +except digits to compare to the search term.
+ +You can also optionally create a custom filter condition that doesn't require a term to be provided by the user - for example if you're matching
+to a variable that you're setting within your code. If you want to do this, you can set noTerm: true
inside the filter, and the filter will
+run even when no term is provided. The filter box will, however, still be shown.
Set the placeholder
property on the filter
object to add a placeholder=""
attribute to the input element. This is set for the "email" and "age" columns below.
Occasionally, you may want to provide two or more filters for a single column. This can be accomplished by setting a filters
array instead of a filter
object.
+The elements of this array are the same as the contents of the filter
object in all the previous examples. In fact, filter: { term: 'xxx' }
is just an alias
+for filters: [{ term: 'xxx' }]
. See the "age" column below for an example.
+
+UI-Grid provides an i18nService that you can use to change the default language, add translations, +or change existing translations.
+ +The easiest way to set language is to use the ui-i18n directive in a div that contains the grid. +
+<div ui-i18n="{{lang}}"> ++Only one ui-i18n directive is allowed. The current language setting is stored in the i18n service (singleton) +so there is currently no way to have more than one language per app. + +
Another option is to use the i18nService and use the setCurrentLang method +
+ i18nService.setCurrentLang('fr'); ++ +
For an example using angular-translate to translate your headers, refer to http://plnkr.co/edit/KnrKTst5dWXvlZNeIy9c?p=preview
+
+The grid supports a grid footer row, which can be displayed if showGridFooter option is set to true (default=false). This footer displays
+Total rows in the grid and the number of filtered rows. Selected rows will be shown if using ui-grid-selection feature.
+
The grid also has column footer, which can be displayed if showColumnFooter option is set to true (default=false).
+You can set an aggregation function for each column or use a custom footer template to display
+what ever aggregation you wish.
+Aggregation functions supported are: sum, avg, row count, min, max.
+You need to inject the uiGridConstants in order to use aggregationTypes enum.
+You can also pass in a function in order to create your own aggregation logic.
+If you set the aggregationHideLabel
option on the columnDef to true, then the column will
+show the aggregation but without a label. Refer the third column in the example below.
+You can override the default grid footer template with gridOptions.footerTemplate. Copy the default ui-grid-footer.html from the source as your starting point. This is not demonstrated in this tutorial.
+
+UI-Grid supports complex object binding in the colDef.field.
+ +This tutorial shows two-way binding to properties with special characters, array element, nested property, and function.
+ +Note that a function can not be edited.
+ +In your custom cellTemplates, you can use:
+
+COL_FIELD which will be replaced with grid.getCellValue(row). This should be used for any cellTemplates that need readonly access to the field value
+
+MODEL_COL_FIELD which will be replaced with row.entity.field. Use MODEL_COL_FIELD anytime you need ng-model='field'
+
+A common problem when using UI Grid is the need to put the grid in a place that is not visible on initial page load, such as in a tab in a tabset, or inside an accordion. +The end result is often a grid that appears to be rendered "incorrectly" with squished columns or unexpected widths or heights. Let's start with this scenario:
+ +Imagine you have a container <div>
that you're going to put your grid in. This container has no width or height, it's simply going to hold your grid. You supply the grid
+no CSS class so its height
and width
CSS values are set to auto
, which means the browser will calculate them automatically.
UI Grid is built from the ground-up to +make displaying and traversing large amounts of data easier. It does this through a process called "virtualization". This means that if you have 10,000 rows and 50 columns, +instead of rendering 500,000 DOM elements and crippling the browser, UI Grid will do its best to only display the minimum amount it needs to, and "fake" the representation to +the user. To them it looks like all the data is right there on the screen when in fact a tiny fraction is being rendered.
+ +This becomes a problem in the scenario we created above, because while we may have told UI Grid that we have 10,000 rows and 50 columns, it knows that we don't actually want +to display them all at once, so it has to figure out the visible space it's allowed to "draw" in. This space is called the "viewport". It's the "port" through which we "view" +the data. Everything outside this viewport is essentially empty space, except for some extra rows and columns that we add to let scrolling appear smoother.
+ +When you put the grid in a container div
that has no height specified, and the grid has no height specified with CSS, then it has no idea how big you want it to be.
+Do you want to see 5 rows? 100? It cannot tell. Currently it just assumes that 10 rows is a good default value and resizes itself to fit that number of rows in its viewport.
+You can alter this value with the minRowsToShow
option.
What if you give the grid a height of 100%
but its parent container div
has no height? Then once again it has no idea how big it's supposed to be. 100% of what? Nothing?
+Once again, it will simply resize itself to fit the number of rows specified by minRowsToShow
. You can fix this problem by either giving the grid a specific height, or a variable height and then giving its container a valid height or putting other elements in the container that are present before the grid is linked up, i.e if there's an image in the same container as the grid and the image is 500px high, and the grid is specified to have height: 50%
, then it will be 250px high.
Let's move on to the case of a grid that is hidden , whether in an tab or an accordion or behind an ng-show
expression. Hidden elements have no height or width, because they are unrendered. If you were to take an element with the CSS property display: none
on it, and log out its offsetWidth
property, it would be 0. Even if when it is visible it's 1000px high, when it's hidden it has no height. UI Grid gets around this in the same way that jQuery does: cheating. It creates a "fake" clone of the element in question, changes display: none
to visibility: hidden
(which means it is not visible but still takes up space), appends this cloned element to the <body>
, calculates its height, then removes it. The <body>
is the only safe place to do this because when a hidden element takes up space it can move the flow other elements around and cause a "flickering" effect.
So when you put the grid in a place that's not rendered, and don't give the grid any specific CSS to tell it how big it has to be, when it creates this cloned element and measures it, it will have 0 height and 0 width. The grid will do its best to resize, i.e. 0px is too small to be usable so it'll resize its height as above, but there's really no way to tell how wide it should be, so the columns all end up being their minimum width (30px or so), the viewport will be tiny, and it looks like the grid has rendered incorrectly because while the viewport is very small the grid element itself will just take up the
+width of the parent container, which is what the auto
value for width tells elements to do.
In order to fix this you must give the grid something to work with. Here are some options:
+ +ng-if
to prevent the grid from being rendered until the element it is in (tab/accordion/etc) is active, as in this plunker.autoResize
feature let the grid redraw itself as needed. This may cause some flickering as the check is on a 250ms cycle. Here's is a plunker demonstrating this.It's all up to you. And if you know a better way then please submit it in an issue or a pull request. There's always room for improvement and innovation.
+
+Using multiple grids on a single page
+
+
+
+Using a grid in a modal popup
+
+A class name or function returning a class name can be assigned to each columnDef.
+ +In this example, we will override the color and background for the first column and color the company text blue if it equals 'Velity'.
+ +
+
+You can swap out data in the grid by simply providing a different reference.
+
+You can dynamically add and remove columns, the grid watches the column defs and +updates appropriately. The columns are by default shown in the order of the columnDefs, +although if/when the column move feature arrives the end user will be able to alter +that default.
+ +You can dynamically change the display name on a column (along with some other column +def properties), and call the notifyDataChange api to force an update.
+
+You can add a row header column, which goes into the left pinned container +(right pinned container on RTL implementations).
+
+A class name or function returning a class name can be assigned to each columnDef.
+ +In this example, we will set the font color of header column 0 to blue, and conditionally +set the background and foreground color of the header if the sort direction is ASC
+ +
+
+We get a lot of questions about fonts, and it seems to be a pain point for many users. To date all +of these problems have been diagnosed to a misconfigured build setup for the end user.
+ +This tutorial addresses the basics of the configuration.
+ +Key points here are:
+ +plunkr.co
. The fact that plunkrs don't show fonts doesn't make this
+an issue with ui-grid, it's still likely an issue with your configuration. :-)When you download and install ui-grid, you'll get a series of files:
+ +ui-grid.js
ui-grid.css
ui-grid.eot
ui-grid.svg
ui-grid.ttf
ui-grid.woff
The last four files are the font files. These generally need to be inserted into your local build process,
+as they will need to be copied to your assets
folder, your fonts
folder or some other similar location.
+The instructions for configuring this are different for each build setup, but your build environment should
+have come with some method for configuring this - usually a Gruntfile.js, a build.config.js, or some form of
+gulp configuration.
If you are seeking to diagnose why you're not getting your fonts, you can typically look at your Chrome developer +tools, and you'll see a network request being issued for one of the font files, which then gives an error not +found. As a first step, try manually copying the font files into that location. If your application now works, +then your goal is to work out how to configure your build process to do that automatically for you.
+
+The grid supports RTL languages
+
+i18n
+
+This grid example uses a data set of 10,000 records.
+ +Demonstrates the following:
+ +
+
+You can swap out data in the grid by simply providing a different reference.
+
+Demonstrating scrolling with large amount of columns
+
+In this example we create the most basic grid possible.
+ +
We are using the optional external-scopes="{myViewModel:myViewModel}"
attribute
+to give us access to the view model via $scope.getExternalScopes() function.
+
+Appends data to the grid every .2 seconds for 15 seconds. This emulates loading pages of data from the server. It's also an example
+of setting gridOptions.data to a string value that specifies an object on your scope to watch.
+
All features are enabled to get an idea of performance
+
+The core module, ui.grid, contains only the core features needed. More complex features are separated into feature modules and must be included in your application module.
+ +You must include each feature as a dependency for your application module. +
+angular.module('yourApp', ['ui.grid', 'ui.grid.feature1', 'ui.grid.feature2']); ++ +
Each feature directive must also be included in alongside the ui-grid directive. This allows you to have many grids in an application and enable certain features for each grid instance. +
+<div ui-grid="gridOptions" class="grid" ui-grid-feature1 ui-grid-feature2></div> +
+
+The ui.grid.edit feature allows inline editing of grid data. To enable, you must include the 'ui.grid.edit'
module
+and you must include the ui-grid-edit
directive on your grid element.
You can use the enableCellEdit
options in your column definitions to allow a column to be editable.
Editing is invoked via double-click, f2, or start typing any non-navigable key. Cell editing ends on tab, enter or esc (cancel) +on an input editor, and tab, left or right arrows, enter or esc for a dropdown.
+ +By default an input element is provided, with numeric, date and checkbox editors for fields specified as 'number'
, 'date'
+and 'boolean'
types, for all other fields a simple text editor is provided. (A point to note about date editors is that for date editors
+to be enabled the datatype of the variable should also be "Date").
A dropdown editor is also available, through setting the editableCellTemplate
on the columnDef
to 'ui-grid/dropdownEditor'
.
+When using a dropdown editor you need to provide an options array through the editDropDownOptionsArray
property on the columnDef
.
+This array by default should be an array of {id: xxx, value: xxx}
, although the field tags can be changed through
+using the editDropdownIdLabel
and editDropdownValueLabel
options.
Custom edit templates should be used for any editor other than the default editors, but be aware that you will likely also
+need to provide a custom directive similar to the uiGridEditor directive so as to provide BEGIN_CELL_EDIT, CANCEL_CELL_EDIT
+and END_CELL_EDIT
events.
ColumnDef Options:
+ +editableCellTemplate
(default: 'ui-grid/cellEditor'
) - Valid html, templateCache Id, or url that returns html
+content to be compiled when edit mode is invoked.enableCellEdit
(default: false
for columns of type 'object'
, true
for all other columns) - true
will enable
+editing and false
will disable it.cellEditableCondition
(default: true
) Can be set to a boolean or a function that will be called with the cellScope
+to determine if the cell should be invoked in edit mode.type
(default: 'string'
) If set to 'number'
, 'boolean'
or 'date'
the default editor provided for editing will be numeric
+or boolean or date editor respectively. If set to 'object'
the column will not be editable by default. Be aware that this
+type
column is also used for other purposes within ui-grid, including the sorting logic.editDropdownOptionsArray
If a dropdown, needs to be populated with an array of values, by default those values should be
+{id: xxx, value: xxx}
, the labels can be adjusted with the next two optionseditDropdownIdLabel
(default: 'id'
) Controls the id label in the options array - so if your array happens to contain
+'code'
instead you can use it without having to reprocess the arrayeditDropdownValueLabel
(default: 'value'
) Controls the value label in the options array - if your array happens to
+contain 'name'
instead you can use it without having to reprocess the arrayeditDropdownRowEntityOptionsArrayPath
can be used as an alternative to editDropdownOptionsArray when the contents of the dropdown depend on the entity backing the row.editDropdownFilter
(default: ''
) Allows you to apply a filter to the values in the edit dropdown options, for example
+if you were using angular-translate you would set this to 'translate'
The following option is available only if using cellNav feature
+ +enableCellEditOnFocus
- true
to invoke editor as soon as cell has focusNote that the edit functionality uses native html5 edit widgets - the date picker, the dropdown and the input box +itself. If your browser does not implement these widgets, then you won't get them. If your browser implements these +widgets in a way that isn't ideal (for example, some browsers don't allow number fields to start with '.', so you can't +type in '.5'), then you need to provide a custom editor instead. On the medium term roadmap there is intent +to provide a bootstrap feature, which would provide directives that were compatible with angular-bootstrap directives, +allowing use of the bootstrap datepicker and input fields
+ ++$scope.gridOptions.columnDefs = [ + { name: 'name', enableCellEdit: true, editableCellTemplate: 'xxxxxxx' }, + { name: 'age', enableCellEdit: true, type: 'number'}, + { name: 'registered', displayName: 'Registered' , type: 'date'}, + { name: 'address', displayName: 'Address', type: 'object'}, + { name: 'address.city', enableCellEdit: true, displayName: 'Address (even rows editable)', cellEditableCondition: function($scope){return $scope.rowRenderIndex%2} } + { name: 'isActive', enableCellEdit: true, type: 'boolean'}, +] +
+
+This grid example uses the ui-grid-cellNav directive to add cell navigation. To enable, you must include the 'ui.grid.cellNav' +module and you must include the ui-grid-cellNav directive on your grid element.
+ +CellNav allows you to navigate around the grid using the arrow keys, pg-down and pg-up, enter (moves down),
+shift-enter (moves up), tab (moves right) and shift-tab (moves left). When combined with the editable feature, the
+left-right arrow keys will be subsumed when in "deep edit" mode, allowing you to move around within the text you're editing.
+The tab key and shift-tab keys continue to function.
Uses the gridOptions.onRegisterApi callback to register the on_cellNav event and log when the cell is navigated.
+ +Provides an example of requesting a scroll to a specific row or column programatically - useful for +remembering the state of a page and scrolling back to that position when a user returns.
+ +Provides an example of scrolling to and setting the focus on a specific cell, using the scrollToFocus api.
+ +Provides an example of utilizing the modifierKeysToMultiSelectCells option and getCurrentSelection API function to +extract values of selected cells.
+
+UI-Grid uses isolate scope, so there is no access to your application scope variables from a row or cell template.
+ +To access your application data within UI-Grid, use the external-scopes attribute. Give the attribute a +property that exists on your scope. +
+ $scope.myViewModel.showMe = function(){...}; + <div ui-grid="{ data: myData }" external-scopes="myViewModel" class="grid"></div> ++ +
Then in a template, you access the scope using getExternalScopes() function. +
+ ng-click="getExternalScopes().showMe()" +
+
+The pinning feature allows the user to pin a column to left or right. To enable, you must include the 'ui.grid.pinning' module and you must include the ui-grid-pinning directive on your grid element.
+ +It is also possible to disable pinning on column level. Note the 'id' column definition in the example below.
+
+The Resize Columns feature allows each column to be resized.
+ +To enable, you must include the 'ui.grid.resizeColumns' module and you must include the ui-grid-resize-columns directive on your grid element.
+ ++angular.module('yourApp', ['ui.grid', 'ui.grid.resizeColumns']); ++ +
+<div ui-grid="gridOptions" class="grid" ui-grid-resize-columns></div> ++
+$scope.gridOptions = { + enableColumnResizing: false +}; ++
enableColumnResizing
property to false in its column definition.
++$scope.gridOptions = { + enableColumnResizing: true, + columnDefs: [ + { field: 'name' }, + { field: 'gender', enableColumnResizing: false }, + { field: 'company' } + ] +}; +
+
+The ui.grid.rowedit extends the edit feature to support callbacks for server saving of the data, +with that data saved "row at a time." This feature attempts to give a user an experience similar +to a spreadsheet - in that they can edit whichever fields they wish, and the feature will seek to +save the data "row at a time". To the extent that the data doesn't generate errors, from a user +perspective the saving is almost invisible - rows occassionally go grey (saving) and can't be edited whilst +grey, but otherwise the user edits as if the data were local.
+ +Each row is in one of four states at any point in time:
+ +isDirty
: Edits have been made, but the data has not yet been saved, either because the
+user is still editing the row or because the timer hasn't triggered as yetisSaving
: The callback to save the row has been called and has not yet returned. The row is
+not editable during this time, and is shown as greyed out, so as to avoid the user
+causing locking exceptions by editing the row againisError
: The save callback returned an error. The row still has the updated data displayed,
+but will be shown in redThe basic method of operation is that whenever a cell is edited (identified using the edit.afterCellEdit
+event) an isDirty
flag is set on the row, and a saveTimer
is set. If another cell in the same row commences
+editing within 2 seconds (or other configurable time), then the timer will be destroyed again. Otherwise
+upon the timer completing the row will be set to a status of isSaving
and greyed out, and the saveRow
+event will be called. The function called by this event must return a promise, and the rowedit feature
+will wait on that promise.
If the cellNav feature is also enabled, then a setFocus on a cell within the row is sufficient to delay +the timer (this more easily deals with situations where only some columns are editable, and a user tabs +or clicks to a non-editable field on the same row).
+ +If the promise is successfully resolved then the row is set back to clean. If the promise is rejected then the
+row is set to a status of isError
.
Optionally, the calling application can request flushDirtyRows
, which will trigger the save event for all
+currently dirty rows. If the rowEditWaitInterval
grid option is set to -1, then saves will never be triggered
+by timer, and only be triggered when manually called.
Methods and properties are provided to operate with this regime:
+ +getDirtyRows()
: returns an array of gridRows of all currently dirty rowsgetErrorRows()
: returns an array of gridRows of all currently errored rowsflushDirtyRows()
: flushes all currently dirty rows to the server, might be used
+on a page navigation request or pressing of a save buttonsaveRow( rowEntity )
: event called when a row is ready for savingrowEditWaitInterval
: grid option that controls how long a wait will be before a save is triggered (in ms)In this example rows are saved 2 seconds after moving off, and the save is faked using a timeout of 3 seconds, so +each save will take 3 seconds to complete. Any row saved with a gender of "male" will error.
+ +
+
+The exporter feature allows data to be exported from the grid in +csv or pdf format. The exporter can export all data, visible data or selected data.
+ +To use the exporter you need to include the ui-grid-exporter directive on +your grid. If you want to export selected rows you must include the ui-grid-selection +directive on your grid. If you want to export as PDF you need to have installed pdfMake, +available through: +
bower install pdfmake+ +
The options and API for exporter can be found at ui.grid.exporter.
+ +The exporter adds menu items to the grid menu, to use the native UI you need to enable
+the grid menu using the gridOption enableGridMenu
Note that the option to export selected data is only visible if you have data selected.
In this example we use the native grid menu buttons, and we show both the pdf and csv export options.
+ +
+
+The importer feature allows data to be imported into the grid in +csv or json format. The importer can use the native grid menu, or can accept a +file from a custom file picker implemented by the user.
+ +The importer imports files in json or csv format, with the ability to be extended +to other file formats if demand exists.
+ +For json format files the received elements are assumed to match the column.field +attributes in your columnDefs, and are loaded into the provided entity.
+ +For csv files the data is mapped to the columnDefs, with columns in the heading row in the
+csv needing to match either the column.name or column.displayName. Optionally you can
+provide a custom function that maps headings to column.name, and this will be used instead,
+you could use this to implement a custom "column picker" type routine. If you are using
+internationalisation on the headers (say, via adding a cellHeaderFilter), then you can
+also optionally pass a filter function into the importerHeaderFilterCallback
routine.
+This routine will be called on the displayName to try to match the translated text, if
+you provide this routine it must return an immediate translation, not a promise - so if
+using angular-translate you need to use $translate.instant
.
Optionally you can provide a custom function that maps the data within each entity as it is
+imported, refer the documentation for importerObjectCallback
.
To use the importer you need to include the ui-grid-importer directive on
+your grid, and you must provide a gridOptions.importerDataAddCallback
function that adds
+the created objects into your data array. You also need to have installed the csv-js library,
+bower install --savedev csv-js
. You can configure the csv-js library through use of globals,
+for example CSV.DETECT_TYPES = false;
, refer to the {https://github.com/gkindel/CSV-JS csv-js documentation}
+for more information.
The options and API for importer can be found at ui.grid.importer.
+ +The importer adds menu items to the grid menu, to use the native UI you need to enable
+the grid menu using the gridOption enableGridMenu
. You can turn the menu items off by
+setting importerShowMenu: false
.
In this example we use the native grid menu to import a file. You need to provide a file +from your file system to use the tutorial, you can copy either import.json or import.csv +as a test file.
+ +The grid will start empty, and will auto-populate the column defs and data once the file +has been imported (in many uses you would define the column defs up front, this example +illustrates that doing so is not mandatory).
+ ++[{"Name":"John Smith","Gender":"male","Company":"TestIcon"},{"Name":"Jane Doe","Gender":"female","Company":"FastTruck"}] ++ +
import.csv +
+"Name","Gender","Company" +"John Smith","male","TestIcon" +"Jane Doe","female","FastTrucks" +
+
+The save state feature allows you to save the current look of the grid +and restore it upon returning to the grid.
+ +For example, you may have an application where your user can reorder the columns, +adjust column widths, apply sorts and filters, and select a specific cell. The user +might adjust their grid to look as they wish, and then navigate to another page. When +the user returns to the page with the grid, they might expect it to look like it +did when they left. The save state feature permits this.
+ +There are two core methods:
+ +Note that the saveState functionality deliberately sets out to store the transient state - +the information that isn't held in gridOptions nor columnDefs. The calling application is responsible for +storing gridOptions and columnDefs (and must have had them in order to render the grid the first time).
+ +The feature also provides some options that control what is saved. All options are true by +default:
+ +In this example we provide a button to save the grid state. You can then modify the grid layout +to something different, and use the restore button to set the grid back the way it was.
+ +
+
+This grid example uses the ui-grid-selection directive to row selection. To enable, you must include the 'ui.grid.selection' +module and you must include the ui-grid-selection directive on your grid element.
+ +Uses the gridOptions.onRegisterApi callback to register the rowSelectionChanged event and log when the row is selected.
+By default the selection feature will divide selection changes into batch events and single events, there are
+two different events provided in the api. By setting enableSelectionBatchEvent: false
you can cause it to
+instead just call the single event in a loop - which may be useful if you're doing client rather than server
+side processing of the changes.
By default the module will provide a row header with checkboxes that allow selection of individual rows. If you set
+the enableRowHeaderSelection
gridOption to false, then the row header is hidden and a click on the row will
+result in selection of that row. You can see that in this tutorial for grid1 by looking at your javascript console.
Setting the multiSelect
gridOption to true will allow selecting multiple rows, setting to false will allow selection
+of only one row at a time.
If you have multiSelect: false
, then an additional option noUnselect
will mean that a row is always selected. You
+can select a different row (which will unselect the current row), but you cannot manually unselect the current row
+by clicking on it. This means that at least one row is always selected, other than when you first open the grid. If
+necessary you could programatically select the first row upon page open. The second grid demonstrates this.
If multiSelect: true
, another option modifierKeysToMultiSelect
may be used. If set to true this will allow selecting
+multiple rows only if the Ctrl-Key, Cmd-Key (Mac) or the Shift-Key is used when selecting, if set to false then it allows
+selecting multiple rows by individually clicking them.
By default a selectAll box is shown at the top of the rowHeader. If multiSelect: true
is set then this will allow
+you to select all visible rows. Note that the selectAll does not watch for new data, so if you are using the selectAll
+function and you add data to the grid, you need to check grid.api.selection.getSelectAllState
, and if it is currently
+ticked, then manually call grid.api.selection.selectAllVisibleRows
after your data has been added.
The selectAll box can be disabled by setting enableSelectAll
to false.
You can set the selection row header column width by setting 'selectionRowHeaderWidth' option.
Two examples are provided, the first with rowHeaderSelection and multi-select, the second without.
+ +
+
+This grid example puts two grids on the same page, and demonstrates that they are isolated from +each other.
+
+The infinite scroll feature allows the user to lazy load their data to gridOptions.data
+ +Specify percentage when lazy load should trigger: +
+ $scope.gridOptions.infiniteScroll = 20; +
+
+The auto-resize feature will enable the grid to resize itself when its container changes size.
+ +To use, include the 'ui.grid.autoResize'
module in your angular app's dependencies, and add the ui-grid-auto-resize
directive to your grid element.
Note: This works by adding a checker on 250ms interval that sees if the grid element has changed in size. It could potentially affect the performance of your site or application negatively.
+
+When pagination is enabled, the data is displayed in pages that can be browsed using using the built in pagination selector.
+
+When paging is enabled, the data is displayed in pages that can be browsed using using the built in paging selector.
+
+The grid supports RTL languages
+
+You can swap out data in the grid by simply providing a different reference.
+
+Create a grid almost the same as the most basic one, but with a custom row template.
+ +You can use grid.appScope in your row template to access +elements in your controller's scope. More details are on +the external scopes tutorial.
+
+Combine ui.grid.edit with ui.grid.cellNav, you can enable editing when the cell gets focus.
+grid.options.enableCellEditOnFocus must be set to true.
+
+See api docs for default navigation keys and how you can override.
+
+Create a grid almost the same as the most basic one, but with a custom header
+
+You can customize a column's menu and provide your own functionality. Each menu item can have:
+ +shown
: a function that determines whether or not to display the itemtitle
: the title you'd like to have displayed for the menu item (note that you can also
+use i18n on this through the gridMenuTitleFilter
setting)icon
: an icon you'd like displayed alongside the itemaction
: a function that will be called when the menu is clickedactive
: a function that highlights the item (giving a toggle type effect - see the sort on the column menus for an example)context
: by default, the action
, shown
and active
's' contexts will have a reference to the grid added as the
+property grid
(accessible through this.grid
. You can pass in your own context by supplying
+the context
property to your menu item. It will be accessible through this.context
.The column menu will add the column's GridColumn
object to the context as this.context.col
.
+You can then show/hide the the menu item based on conditions that use the grid and column. You could
+also use a custom column builder to add some item to the every column definition.
You can remove the column hide option using the enableHiding: false
columnDef option, which will also prevent
+this column being hidden in the gridMenu (once it is finished and merged). You can disable
+the column menu entirely using the enableColumnMenu: false
columnDef option.
You can disable all column menus using the enableColumnMenus: false
grid option.
You can supply an icon class with the icon
property.
See the example below for usage.
+
+
+
+The grid menu can be enabled through setting the gridOption enableGridMenu
. This
+adds a settings icon in the top right of the grid, which floats above the column header. The
+menu by default gives access to show/hide columns, but can be customised to show additional
+actions.
You can customize a the menu and provide your own functionality. Each menu item can have:
+ +shown
: a function that determines whether or not to display the itemtitle
: the title you'd like to have displayed for the menu item (note that you can also
+use i18n on this through the gridMenuTitleFilter
setting)icon
: an icon you'd like displayed alongside the itemaction
: a function that will be called when the menu is clickedactive
: a function that highlights the item (giving a toggle type effect - see the sort on the column menus for an example)context
: by default, the action
, shown
and active
's' contexts will have a reference to the grid added as the
+property grid
(accessible through this.grid
. You can pass in your own context by supplying
+the context
property to your menu item. It will be accessible through this.context
.The exporter feature also adds menu items to this menu. The exporterMenuCsv
option is set
+to false, which suppresses csv export. The 'export selected rows' option is only available
+if at least one row is selected.
The column titles can have a custom filter defined using gridMenuTitleFilter
, used when your
+column headers have an internationalization filter (angular translate or i18nService), and you
+want them also internationalized in the grid menu. The translate needs to return either a string,
+or a promise that will resolve to a string. In the example below we create a fake
+internationalization function that waits 1 second then prefixes each column with "col: ".
You can suppress the ability to show and hide columns by setting the gridOption gridMenuShowHideColumns: false
,
+you can suppress the ability to hide individual columns by setting enableHiding
on that columnDef to false.
+
+UI-Grid uses isolate scope, so there is no access to your application scope variables from a row or cell template.
+By default, the parent scope of the ui-grid element is assigned to a property on $scope.grid named appScope.
+
+
+If you need another reference other than $scope.$parent, then use gridOptions.appScopeProvider. This reference
+will be assigned to grid.appScope.
+
+
+$scope.grid.appScope is available in all templates that the grid uses.
+
+In a template, you access the scope using grid.appScope property
+
+ ng-click="grid.appScope.showMe()" +
+
+UI-Grid uses isolate scope, so there is no access to your application scope variables from a row or cell template.
+ +To access your application data within UI-Grid, use the external-scopes attribute. Give the attribute a +property that exists on your scope. +
+ $scope.myViewModel.showMe = function(){...}; + <div ui-grid="{ data: myData }" external-scopes="myViewModel" class="grid"></div> ++ +
Then in a template, you access the scope using getExternalScopes() function. +
+ ng-click="getExternalScopes().showMe()" +
+
+Module 'ui.grid.expandable' adds the subgrid feature to grid. To show the subgrid you need to provide following grid option:
+ ++ $scope.gridOptions = { + //This is the template that will be used to render subgrid. + expandableRowTemplate: 'expandableRowTemplate.html', + //This will be the height of the subgrid + expandableRowHeight: 140, + //Variables of object expandableScope will be available in the scope of the expanded subgrid + expandableRowScope: expandableScope + } ++ +
rowExpandableTemplate will be template for subgrid and expandableRowHeight will be height of the subgrid, expandableRowScope can be used +to added variables to scope of expanded grid. These variables can then be access from expandableRowTemplate. The grid api +provided following events and methods fos subGrids:
+ ++ //rowExpandedStateChanged is fired for each row as its expanded: + gridApi.expandable.on.rowExpandedStateChanged($scope,function(row){ + }); + //Following methods can be used to expand/ collapse all rows of the grid: + gridApi.expandable.expandAllRows(); + gridApi.expandable.collapseAllRows(); ++ +
SubGrid nesting can be done upto multiple levels.
+
+The exporter feature allows data to be exported from the grid in +csv or pdf format. The exporter can export all data, visible data or selected data.
+ +To use the exporter you need to include the ui-grid-exporter directive on +your grid. If you want to export selected rows you must include the ui-grid-selection +directive on your grid. If you want to export as PDF you need to have installed pdfMake, +available through: +
bower install pdfmake+ +
The options and API for exporter can be found at ui.grid.exporter.
In this example we provide a custom UI for calling the exporter, and we tailor +the way the returned data behaves.
+ +
+
+Sometimes you want to sort data externally, either on the client using custom sort routines (that are over and +above those you can achieve with custom sort functions, or on the server as part of +your data retrieve.
+ +UI-Grid provides two functions that support this, firstly a sortChanged event that tells you when a user has +changed their requested sort using the grid ui, and secondly a useExternalSorting property to turn off +the grid native sorting.
In this example we suppress the internal sorting, and emulate an external sort by picking one of three +json files to show - one sorted ASC, one sorted DESC, and one not sorted. To further illustrate that this +is using external sorting, the external sort routine (consisting of me manually editing json files) got bored +of sorting after the first 10 or so rows.
+ +We allow sorting by the first and second columns, so that we can show a default sort and that default sort +indicator being removed even when using external sorting. Our external sort routine ignores that second +column however, so sorting by it has no effect.
+ +
+
+Sometimes you want to filter data externally, either on the client using custom filter routines, or on +the server as part of your data retrieve.
+ +UI-Grid provides two functions that support this, firstly a filterChanged event that tells you when a user has +changed their requested filter using the grid ui, and secondly a useExternalFiltering property to turn off +the grid native filtering.
In this example we suppress the internal filtering, and emulate an external filter by picking one of three +json files to show - one filtered by gender 'male', one filtered by gender 'female', and one not sorted.
+ +To further illustrate that this is using external sorting, the external filter routine (consisting of me manually +editing json files) got bored of filtering after the first 10 or so rows, and deleted all other rows in the file.
+ +
+
+For the basics of editing refer 201 editable, this tutorial provides an example +of combining cellNav and edit to give a more spreadsheet-like experience.
+
+Feature ui.grid.moveColumns allows moving column to a different position. To enable, you must include the ui.grid.moveColumns
module
+and you must include the ui-grid-move-columns
directive on your grid element.
By default column moving will be enabled for all the columns of the grid. To disable it for all columns of grid property enableColumnMoving
+of grid options can be used. To specifically enable or disable column moving for a specific column property enableColumnMoving
+of column definitions can be used.
Columns can be repositioned by either dragging and dropping them to specific position. Alternatively, gridApi method
+gridApi.colMovable.moveColumn(oldPosition, newPosition)
can also be used to move columns. The column position ranging from 0
+(in the leftmost) up to number of visible columns in the grid (in the rightmost).
+
+Feature ui.grid.moveColumns allows moving column to a different position. To enable, you must include the ui.grid.moveColumns
module
+and you must include the ui-grid-move-columns
directive on your grid element.
By default column moving will be enabled for all the columns of the grid. To disable it for all columns of grid property enableColumnMoving
+of grid options can be used. To specifically enable or disable column moving for a specific column property enableColumnMoving
+of column definitions can be used.
Columns can be repositioned by either dragging and dropping them to specific position or by using this gridApi method
+gridApi.colMovable.moveColumn(oldPosition, newPosition)
.
+
+The importer feature allows data to be imported into the grid in +csv or json format. The importer can use the native grid menu, or can accept a +file from a custom file picker implemented by the user.
+ +The importer can work together with the rowEdit feature to automatically save the +imported rows to your server, and show validation errors for any rows that were +not accepted by the server.
+ +If you want to allow the user to look at the data before the saves kick off, consider +setting the rowEditWaitInterval to -1, which will suppress the auto-save, and require +you to manually call flushDirtyRows() once the user has made a save request.
In this example we use the rowEdit feature to save the rows as they are created. +As with the rowEdit tutorial, any records with a gender of 'male' give an error, +and can be edited to save without error.
+ ++[{"name":"John Smith","gender":"male","company":"TestIcon"},{"name":"Jane Doe","gender":"female","company":"FastTruck"}] ++ +
import.csv +
+"Name","Gender","Company" +"John Smith","male","TestIcon" +"Jane Doe","female","FastTrucks" +
+
+The importer feature allows data to be imported into the grid in +csv or json format. The importer can use the native grid menu, or can accept a +file from a custom file picker implemented by the user.
+ +The importer can work together with the rowEdit feature to automatically save the +imported rows to your server, and show validation errors for any rows that were +not accepted by the server.
+ +If you want to allow the user to look at the data before the saves kick off, consider +setting the rowEditWaitInterval to -1, which will suppress the auto-save, and require +you to manually call flushDirtyRows() once the user has made a save request.
+ +In this example we also use a custom file picker to trigger the import, as well +as the grid menu.
In this example we use the rowEdit feature to save the rows as they are created. +As with the rowEdit tutorial, any records with a gender of 'male' give an error, +and can be edited to save without error.
+ ++[{"name":"John Smith","gender":"male","company":"TestIcon"},{"name":"Jane Doe","gender":"female","company":"FastTruck"}] ++ +
import.csv +
+"Name","Gender","Company" +"John Smith","male","TestIcon" +"Jane Doe","female","FastTrucks" +
+
+The exporter feature allows data to be exported from the grid in +csv or pdf format. The exporter can export all data, visible data or selected data.
+ +To use the exporter you need to include the ui-grid-exporter directive on +your grid. If you want to export selected rows you must include the ui-grid-selection +directive on your grid. If you want to export as PDF you need to have installed pdfMake, +available through: +
bower install pdfmake+ +
The options and API for exporter can be found at ui.grid.exporter.
In this example we provide a custom UI for calling the exporter, and we tailor +the pdf layout to have different styles than the default.
+ +We also apply a filter to the headers (simulating internationalisation), and we reprocess the grid +data to apply a filter to decode coded data.
+ +We also right align the gender column.
+ +
+
+Combining AngularJS with other frameworks/tools sometimes requires changing the normal interpolation symbols ({{
and }}
) to something else, like [[
or %
.
+
+
+UI Grid will automatically detect the change and transform any default symbols in the templates it uses to the custom values. This means that in the unlikely event
+you're expecting to use {{
or }}
to signify something in your custom-interpolation-symbol application, then inside the grid your stuff will likely break.
+
+When pagination is enabled, the data is displayed in pages that can be browsed using using the built in +pagination selector.
+ +For external pagination, implement the gridApi.pagination.on.paginationChanged
callback function. The callback
+may contain code to update any pagination state variables your application may utilize, e.g. variables containing
+the pageNumber
and pageSize
. The REST call used to fetch the data from the server should be called from within
+this callback. The URL of the call should contain query parameters that will allow the server-side code to have
+sufficient information to be able to retrieve the specific subset of data that the client requires from the
+entire set.
It should also update the $scope.gridOptions.totalItems
variable with the total count of rows that exist (but
+were not all fetched in the REST call mentioned above since they exist in other pages of data).
This will allow ui-grid to calculate the correct number of pages on the client-side.
This shows combined external pagination and sorting. +
+
+When paging is enabled, the data is displayed in pages that can be browsed using using the built in paging selector.
+ +For external paging, handle the pagingChanged' to load the page. Set the
gridOptions.totalItems' in the callback.
This shows combined external paging and sorting. +
+
+It is possible to use the edit feature and the dropdown options array to build a cascading +dropdown setup, where the options in the second dropdown depend on the selection in the +first dropdown. Thanks to @ajain27 for the sample code.
+
+The general AngularJS approach is two way data binding. On every digest cycle every binding is evaluated +to see if anything has changed, and updates made as appropriate.
+ +ui-grid explicitly seeks to avoid this. The grid is intended to render large numbers of rows (10K plus), and +if all of those rows were evaluated on every digest cycle the performance would be unworkable. There are +two different approaches that ui-grid takes to improve performance:
+ +notifyDataChange
. This includes elements such as cellClasses
+and rowTemplatesIf you frequent the development gitter you'll see this referred to as "removing watches" - the aim being to +reduce the amount of work needed in each digest cycle, because scrolling calls digest a large number of times.
+ +The main upshot of this is that in some instances you'll need to explicitly tell ui-grid that your data has changed. +This includes:
+ +notifyDataChange
when data is edited
+in place, for other changes to the data you need to call notifyDataChange
manually Examples of notifyDataChange can be found in other tutorials:
+ +
+
+This grid example uses a data set of 10,000 records.
+ +Demonstrates the following:
+ +
+
+Create a grid almost the same as the most basic one, but with a custom row template.
+ +You can use external scopes in your row template to access elements in your controller's scope. The external-scopes
attribute on the grid will expose the property on your $scope
with that name in the grid's isolate scope. You can then use getExternalScopes()
in your row template to access that. More details are on the external scopes tutorial.
+
+Appends data to the grid every .2 seconds for 15 seconds. This emulates loading pages of data from the server. It's also an example
+of setting gridOptions.data to a string value that specifies an object on your scope to watch.
+
All features are enabled to get an idea of performance
+
+You can reference a property to see if grid is in a scroll event.
+
+Testing the grid with protractor can be complex. In order to assist the project provides a
+gridTestUtil.scenario.js
helper library that abstracts some of the key functions, such as
+checking the value of a specified cell in the grid, counting the rows in the grid, or
+clicking specific elements of the grid.
The latest version of this is held in git, and there is potential it could get out of +synch with this tutorial, it is therefore worthwhile to check that file itself at +github e2e test folder
+ +The use of this library involves declaring it at the top of each scenario that requires +grid test functions: +
+ var gridTestUtils = require( './gridTestUtils.scenario.js'); ++ +
Within your tests you can then use the helper methods that are provided: +
+ gridTestUtils.expectRowCount( 'myGrid', 3 ); ++ +
For documentation of the available methods, refer to gridTest
+
+Create a grid almost the same as the most basic one, but with a custom header
+
+UI-Grid allows you to filter rows. Just set the enableFiltering
flag in your grid options (it is off by default).
Sorting can be disabled at the column level by setting enableFiltering: false
in the column def. See the last column below for an example.
+
+UI-Grid allows you to sort rows. The feature is on by default. You can set the enableSorting
flag in your grid options to enable/disable it.
+ Note: You must include ngAnimate in your application if you want the menu to slide up/down, but it's not required. +
+ +Sorting can be disabled at the column level by setting enableSorting: false
in the column def. See the last column below for an example.
Multiple columns can be sorted by shift-clicking on the 2-n columns. To see it in action, sort Gender then shift-click Name.
+
+You can customize a column's menu and provide your own functionality.
+ +By default, the action
and shown
's' contexts will have a reference to the grid added as the property grid
(accessible through this.grid
.
+You can pass in your own context by supplying the context
property to your menu item. It will be accessible through this.context
.
The column menu will add the column's GridColumn
object to the context as this.context.col
. You can then show/hide the the menu item based
+on conditions that use the grid and column. You could also use a custom column builder to add some item to the every column definition.
You can supply an icon class with the icon
property.
See the example below for usage.
+
+
+
+This grid example uses a data set of 100 records
+
+You can use the enableCellEdit
options in your column definitions to allow a column to be editable.
Editing is invoked via double-click, f2, or start typing any non-navigable key. By default numeric, date and checkbox editors are +provided for fields specified as number, date and boolean types, for all other fields a simple text editor is provided. +Custom edit templates should be used for any editor other than the default editors.
+ +ColumnDef Options:
+editableCellTemplate
(default: 'ui-grid/cellEditor') - Valid html, templateCache Id, or url that returns html content to be compiled when edit mode is invoked.
+enableCellEdit
(default: false for columns of type 'object', true for all other columns) - true will enable editing and false will disable it.
+cellEditableCondition
(default: true) Can be set to a boolean or a function that will be called with the cellScope to determine if the cell should be invoked in edit mode.
+type
(default: 'string') If set to 'number', 'boolean' or 'date' the default editor provided for editing will be numeric or boolean or date editor respectively.
+If set to 'object' the column will not be editable by default.
+
+The following option is available only if using cellNav feature
+enableCellEditOnFocus
- true to invoke editor as soon as cell has focus
+$scope.gridOptions.columnDefs = [ + { name: 'name', enableCellEdit: true, editableCellTemplate }, + { name: 'age', enableCellEdit: true, type: 'number'}, + { name: 'registered', displayName: 'Registered' , type: 'date'}, + { name: 'address', displayName: 'Address', type: 'object'}, + { name: 'address.city', enableCellEdit: true, displayName: 'Address (even rows editable)', cellEditableCondition: function($scope){return $scope.rowRenderIndex%2} } + { name: 'isActive', enableCellEdit: true, type: 'boolean'}, +] +
+
+Hiding grids in tabs or modals
+
+Combine ui.grid.edit with ui.grid.cellNav, you can enable editing when the cell gets focus.
+grid.options.enableCellEditOnFocus must be set to true.
+
+See api docs for default navigation keys and how you can override.
+
+Using multiple grids on a single page
+
+Using a grid in a modal popup
+
+This grid example uses the ui-grid-selection directive to row selection.
+ +Uses the gridOptions.onRegisterApi callback to register the rowSelectionChanged event and log when the row is selected.
+
+Customize rows and columns.
+
+Demonstrating scrolling with large amount of columns
+
+You can add column resizing by including the ui.grid.resizeColumns
feature module in your application's dependencies.
+
+angular.module('yourApp', ['ui.grid', 'ui.grid.resizeColumns']); ++ +
Also, you must add the directive to your grid element. We do this so that you can have several grids on a +page and only pay the resizing overhead if the grid is actually using resizing. Once you use this directive, all +columns automatically allow resizing +
+<div ui-grid="gridOptions" class="grid" ui-grid-resize-columns></div> ++
+$scope.gridOptions = { + enableColumnResizing: false +}; ++
enableColumnResizing
property to false in its column definition.
++$scope.gridOptions = { + enableColumnResizing: true, + columnDefs: [ + { field: 'name' }, + { field: 'gender', enableColumnResizing: false }, + { field: 'company' } + ] +}; +
+
+This grid example uses the ui-grid-cellNav directive to add cell navigation.
+ +Uses the gridOptions.onRegisterApi callback to register the on_cellNav event and log when the cell is navigated.
+
+In this example we create the most basic grid possible. Our
+
+Create a grid almost the same as the most basic one, but with a custom header
+
+This set of tutorials should help you on your way to using ui-grid, and provide answer for a lot of common use-cases. +If you don't see your particular case here then ask on gitter.
+ +Developers are encouraged to submit PR's for any needed tutorials.
+ +The tutorials are numbered according to complexity:
+ +
+
+The infinite scroll feature allows the user to lazy load their data to gridOptions.data
+ +Specify percentage when lazy load should trigger: +
+ $scope.gridOptions.infiniteScroll = 20; +
+
+This grid example uses a data set of 10,000 records
A data grid for AngularJS, part of the AngularUI suite
++ + + Code on Github + + + + + + + Download ( 3.0.0-rc.20-5f155b2 ) + + + + + Tutorial + +
++ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); + + self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]); + self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]); + + self.registerStyleComputation({ + priority: 10, + func: self.getFooterStyles + }); + }; + + Grid.prototype.calcFooterHeight = function () { + if (!this.hasFooter()) { + return 0; + } + + var height = 0; + if (this.options.showGridFooter) { + height += this.options.gridFooterHeight; + } + + if (this.options.showColumnFooter) { + height += this.options.columnFooterHeight; + } + + return height; + }; + + Grid.prototype.getFooterStyles = function () { + var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }'; + style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }'; + return style; + }; + + Grid.prototype.hasFooter = function () { + return this.options.showGridFooter || this.options.showColumnFooter; + }; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * - when the options watch fires, the OPTIONS callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * - OPTIONS calls OPTIONS and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to + * ALL + * @returns {function} deregister function - a function that can be called to deregister this callback + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this }; + + var self = this; + var deregisterFunction = function() { + delete self.dataChangeCallbacks[uid]; + }; + return deregisterFunction; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + if (callback._this) { + callback.callback.apply(callback._this,this); + } + else { + callback.callback( this ); + } + } + }, this); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW || + type === constants.OPTIONS ){ + this.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name columnRefreshCallback + * @methodOf ui.grid.class:Grid + * @description refreshes the grid when a column refresh + * is notified, which triggers handling of the visible flag. + * This is called on uiGridConstants.dataChange.COLUMN, and is + * registered as a dataChangeCallback in grid.js + * @param {string} name column name + */ + Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){ + grid.buildColumns(); + grid.refresh(); + }; + + + /** + * @ngdoc function + * @name processRowsCallback + * @methodOf ui.grid.class:Grid + * @description calls the row processors, specifically + * intended to reset the sorting when an edit is called, + * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT + * @param {string} name column name + */ + Grid.prototype.processRowsCallback = function processRowsCallback( grid ){ + grid.refreshRows(); + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.refresh(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @param {object} options An object contains options to use when building columns + * + * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions. + * + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns(opts) { + var options = { + orderByColumnDefs: false + }; + + angular.extend(options, opts); + + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + // tell updateColumnDef that the column was pre-existing + col.updateColumnDef(colDef, false); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + /*** Reorder columns if necessary ***/ + if (!!options.orderByColumnDefs) { + // Create a shallow copy of the columns as a cache + var columnCache = self.columns.slice(0); + + // We need to allow for the "row headers" when mapping from the column defs array to the columns array + // If we have a row header in columns[0] and don't account for it we'll overwrite it with the column in columnDefs[0] + + // Go through all the column defs + for (i = 0; i < self.options.columnDefs.length; i++) { + // If the column at this index has a different name than the column at the same index in the column defs... + if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) { + // Replace the one in the cache with the appropriate column + columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name); + } + else { + // Otherwise just copy over the one from the initial columns + columnCache[i + headerOffset] = self.columns[i + headerOffset]; + } + } + + // Empty out the columns array, non-destructively + self.columns.length = 0; + + // And splice in the updated, ordered columns from the cache + Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache)); + } + + return $q.all(builderPromises).then(function(){ + if (self.rows.length > 0){ + self.assignTypes(); + } + }); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + var self = this; + + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + // See if the column name already exists: + var foundName = self.getColumn(colDef.field); + + // If a column with this name already exists, we will add an incrementing number to the end of the new column name + if (foundName) { + // Search through the columns for names in the format:
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); + + self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]); + self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]); + + self.registerStyleComputation({ + priority: 10, + func: self.getFooterStyles + }); + }; + + Grid.prototype.calcFooterHeight = function () { + if (!this.hasFooter()) { + return 0; + } + + var height = 0; + if (this.options.showGridFooter) { + height += this.options.gridFooterHeight; + } + + if (this.options.showColumnFooter) { + height += this.options.columnFooterHeight; + } + + return height; + }; + + Grid.prototype.getFooterStyles = function () { + var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }'; + style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }'; + return style; + }; + + Grid.prototype.hasFooter = function () { + return this.options.showGridFooter || this.options.showColumnFooter; + }; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * - when the options watch fires, the OPTIONS callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * - OPTIONS calls OPTIONS and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to + * ALL + * @returns {function} deregister function - a function that can be called to deregister this callback + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this }; + + var self = this; + var deregisterFunction = function() { + delete self.dataChangeCallbacks[uid]; + }; + return deregisterFunction; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + if (callback._this) { + callback.callback.apply(callback._this,this); + } + else { + callback.callback( this ); + } + } + }, this); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW || + type === constants.OPTIONS ){ + this.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name columnRefreshCallback + * @methodOf ui.grid.class:Grid + * @description refreshes the grid when a column refresh + * is notified, which triggers handling of the visible flag. + * This is called on uiGridConstants.dataChange.COLUMN, and is + * registered as a dataChangeCallback in grid.js + * @param {string} name column name + */ + Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){ + grid.buildColumns(); + grid.refresh(); + }; + + + /** + * @ngdoc function + * @name processRowsCallback + * @methodOf ui.grid.class:Grid + * @description calls the row processors, specifically + * intended to reset the sorting when an edit is called, + * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT + * @param {string} name column name + */ + Grid.prototype.processRowsCallback = function processRowsCallback( grid ){ + grid.refreshRows(); + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.refresh(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @param {object} options An object contains options to use when building columns + * + * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions. + * + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns(opts) { + var options = { + orderByColumnDefs: false + }; + + angular.extend(options, opts); + + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + // tell updateColumnDef that the column was pre-existing + col.updateColumnDef(colDef, false); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + /*** Reorder columns if necessary ***/ + if (!!options.orderByColumnDefs) { + // Create a shallow copy of the columns as a cache + var columnCache = self.columns.slice(0); + + // We need to allow for the "row headers" when mapping from the column defs array to the columns array + // If we have a row header in columns[0] and don't account for it we'll overwrite it with the column in columnDefs[0] + + // Go through all the column defs + for (i = 0; i < self.options.columnDefs.length; i++) { + // If the column at this index has a different name than the column at the same index in the column defs... + if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) { + // Replace the one in the cache with the appropriate column + columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name); + } + else { + // Otherwise just copy over the one from the initial columns + columnCache[i + headerOffset] = self.columns[i + headerOffset]; + } + } + + // Empty out the columns array, non-destructively + self.columns.length = 0; + + // And splice in the updated, ordered columns from the cache + Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache)); + } + + return $q.all(builderPromises).then(function(){ + if (self.rows.length > 0){ + self.assignTypes(); + } + }); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + var self = this; + + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + // See if the column name already exists: + var foundName = self.getColumn(colDef.field); + + // If a column with this name already exists, we will add an incrementing number to the end of the new column name + if (foundName) { + // Search through the columns for names in the format:
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); + + self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]); + self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]); + }; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL + * @returns {string} uid of the callback, can be used to deregister it again + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; + return uid; + }; + + /** + * @ngdoc function + * @name deregisterDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description Delete the callback identified by the id. + * @param {string} uid the uid of the function that is to be deregistered + */ + Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) { + delete this.dataChangeCallbacks[uid]; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }, this); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name columnRefreshCallback + * @methodOf ui.grid.class:Grid + * @description refreshes the grid when a column refresh + * is notified, which triggers handling of the visible flag. + * This is called on uiGridConstants.dataChange.COLUMN, and is + * registered as a dataChangeCallback in grid.js + * @param {string} name column name + */ + Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){ + grid.buildColumns(); + grid.refresh(); + }; + + + /** + * @ngdoc function + * @name processRowsCallback + * @methodOf ui.grid.class:Grid + * @description calls the row processors, specifically + * intended to reset the sorting when an edit is called, + * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT + * @param {string} name column name + */ + Grid.prototype.processRowsCallback = function processRowsCallback( grid ){ + grid.refreshRows(); + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var self = this; + var rows = this.rows.filter(function (row) { + return self.options.rowEquality(row.entity, rowEntity); + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + rowhash, + found, + newRow; + if ((self.options.useExternalSorting || self.getColumnSorting().length === 0) && newRawData.length > 0) { + var oldRowHash = self.rowHashMap; + if (!oldRowHash) { + oldRowHash = {get: function(){return null;}}; + } + self.createRowHashMap(); + rowhash = self.rowHashMap; + var wasEmpty = self.rows.length === 0; + self.rows.length = 0; + for (i = 0; i < newRawData.length; i++) { + var newRawRow = newRawData[i]; + found = oldRowHash.get(newRawRow); + if (found) { + newRow = found.row; + } + else { + newRow = self.processRowBuilders(new GridRow(newRawRow, i, self)); + } + self.rows.push(newRow); + rowhash.put(newRawRow, { + i: i, + entity: newRawRow, + row:newRow + }); + } + //now that we have data, it is save to assign types to colDefs + if (wasEmpty) { + self.assignTypes(); + } + } else { + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.options); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + $log.log('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length + 1, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + self.columnBuilders[0](colDef,rowHeaderCol,self.gridOptions) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + self.rowHeaderColumns.push(rowHeaderCol); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + $log.debug('buildColumns'); + var self = this; + var builderPromises = []; + var offset = self.rowHeaderColumns.length; + + //add row header columns to the grid columns array + angular.forEach(self.rowHeaderColumns, function (rowHeaderColumn) { + offset++; + self.columns.push(rowHeaderColumn); + }); + + // Synchronize self.columns with self.options.columnDefs so that columns can also be removed. + if (self.columns.length > self.options.columnDefs.length) { + self.columns.forEach(function (column, index) { + if (!self.getColDef(column.name)) { + self.columns.splice(index, 1); + } + }); + } + + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, index + offset, self); + self.columns.push(col); + } + else { + col.updateColumnDef(colDef, col.index); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + }); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i=0; i
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + $log.log('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + $log.debug('buildColumns'); + var self = this; + var builderPromises = []; + var offset = self.rowHeaderColumns.length; + + // Synchronize self.columns with self.options.columnDefs so that columns can also be removed. + if (self.columns.length > self.options.columnDefs.length) { + self.columns.forEach(function (column, index) { + if (!self.getColDef(column.name)) { + self.columns.splice(index, 1); + } + }); + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + angular.forEach(self.rowHeaderColumns, function (rowHeaderColumn) { + offset++; + self.columns.unshift(rowHeaderColumn); + + // renumber any columns already there, as cellNav relies on cols[index] === col.index + self.columns.forEach(function(column, index){ + column.index = index; + }); + }); + + + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, index + offset, self); + self.columns.push(col); + } + else { + col.updateColumnDef(colDef, col.index); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + }); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i=0; i
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs ) { + $log.error( 'Something is wrong in showHideColumns, there are no columnDefs' ); + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( !colDef.disableHiding ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + $log.error('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['$log', 'gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function ($log, gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + $log.log('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.disableHiding = true; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + $log.debug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var rows = this.rows.filter(function (row) { + return row.entity === rowEntity; + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + newRow; + + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + var rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + var found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.gridOptions); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL + * @returns {string} uid of the callback, can be used to deregister it again + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; + return uid; + }; + + /** + * @ngdoc function + * @name deregisterDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description Delete the callback identified by the id. + * @param {string} uid the uid of the function that is to be deregistered + */ + Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) { + delete this.dataChangeCallbacks[uid]; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var rows = this.rows.filter(function (row) { + return row.entity === rowEntity; + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + newRow; + + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + var rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + var found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.gridOptions); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL + * @returns {string} uid of the callback, can be used to deregister it again + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; + return uid; + }; + + /** + * @ngdoc function + * @name deregisterDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description Delete the callback identified by the id. + * @param {string} uid the uid of the function that is to be deregistered + */ + Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) { + delete this.dataChangeCallbacks[uid]; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var rows = this.rows.filter(function (row) { + return row.entity === rowEntity; + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + newRow; + + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + var rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + var found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.gridOptions); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); + + self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL + * @returns {string} uid of the callback, can be used to deregister it again + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; + return uid; + }; + + /** + * @ngdoc function + * @name deregisterDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description Delete the callback identified by the id. + * @param {string} uid the uid of the function that is to be deregistered + */ + Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) { + delete this.dataChangeCallbacks[uid]; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }, this); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name columnRefreshCallback + * @methodOf ui.grid.class:Grid + * @description refreshes the grid when a column refresh + * is notified, which triggers handling of the visible flag + * @param {string} name column name + */ + Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){ + grid.refresh(); + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var rows = this.rows.filter(function (row) { + return row.entity === rowEntity; + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + rowhash, + found, + newRow; + if ((self.options.useExternalSorting || self.getColumnSorting().length === 0) && newRawData.length > 0) { + var oldRowHash = self.rowHashMap; + if (!oldRowHash) { + oldRowHash = {get: function(){return null;}}; + } + self.createRowHashMap(); + rowhash = self.rowHashMap; + var wasEmpty = self.rows.length === 0; + self.rows.length = 0; + for (i = 0; i < newRawData.length; i++) { + var newRawRow = newRawData[i]; + found = oldRowHash.get(newRawRow); + if (found) { + newRow = found.row; + } + else { + newRow = self.processRowBuilders(new GridRow(newRawRow, i, self)); + } + self.rows.push(newRow); + rowhash.put(newRawRow, { + i: i, + entity: newRawRow, + row:newRow + }); + } + //now that we have data, it is save to assign types to colDefs + if (wasEmpty) { + self.assignTypes(); + } + } else { + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.gridOptions); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
+ * gridOptions = { + * gridMenuTitleFilter: $translate + * } + *+ */ + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name showHideColumns + * @description Adds two menu items for each of the columns in columnDefs. One + * menu item for hide, one menu item for show. Each is visible when appropriate + * (show when column is not visible, hide when column is visible). Each toggles + * the visible property on the columnDef using toggleColumnVisibility + * @param {$scope} $scope of a gridMenu, which contains a reference to the grid + */ + showHideColumns: function( $scope ){ + var showHideColumns = []; + if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) { + return showHideColumns; + } + + // add header for columns + showHideColumns.push({ + title: i18nService.getSafeText('gridMenu.columns') + }); + + $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; }; + + $scope.grid.options.columnDefs.forEach( function( colDef, index ){ + if ( colDef.enableHiding !== false ){ + // add hide menu item - shows an OK icon as we only show when column is already visible + var menuItem = { + icon: 'ui-grid-icon-ok', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined; + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + + // add show menu item - shows no icon as we only show when column is invisible + menuItem = { + icon: 'ui-grid-icon-cancel', + action: function($event) { + $event.stopPropagation(); + service.toggleColumnVisibility( this.context.gridCol ); + }, + shown: function() { + return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined); + }, + context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) } + }; + service.setMenuItemTitle( menuItem, colDef, $scope.grid ); + showHideColumns.push( menuItem ); + } + }); + return showHideColumns; + }, + + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name setMenuItemTitle + * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu + * item if it returns a string, otherwise waiting for the promise to resolve or reject then + * putting the result into the title + * @param {object} menuItem the menuItem we want to put the title on + * @param {object} colDef the colDef from which we can get displayName, name or field + * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter + * + */ + setMenuItemTitle: function( menuItem, colDef, grid ){ + var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field ); + + if ( typeof(title) === 'string' ){ + menuItem.title = title; + } else if ( title.then ){ + // must be a promise + menuItem.title = ""; + title.then( function( successValue ) { + menuItem.title = successValue; + }, function( errorValue ) { + menuItem.title = errorValue; + }); + } else { + gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config'); + menuItem.title = 'badconfig'; + } + }, + + /** + * @ngdoc method + * @methodOf ui.grid.gridMenuService + * @name toggleColumnVisibility + * @description Toggles the visibility of an individual column. Expects to be + * provided a context that has on it a gridColumn, which is the column that + * we'll operate upon. We change the visibility, and refresh the grid as appropriate + * @param {GridCol} gridCol the column that we want to toggle + * + */ + toggleColumnVisibility: function( gridCol ) { + gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); + + gridCol.grid.refresh(); + } + }; + + return service; +}]) + + + +.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', +function (gridUtil, uiGridConstants, uiGridGridMenuService) { + + return { + priority: 0, + scope: true, + require: ['?^uiGrid'], + templateUrl: 'ui-grid/ui-grid-menu-button', + replace: true, + + + link: function ($scope, $elm, $attrs, controllers) { + var uiGridCtrl = controllers[0]; + + uiGridGridMenuService.initialize($scope, uiGridCtrl.grid); + + $scope.shown = false; + + $scope.toggleMenu = function () { + if ( $scope.shown ){ + $scope.$broadcast('hide-menu'); + $scope.shown = false; + } else { + $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope ); + $scope.$broadcast('show-menu'); + $scope.shown = true; + } + }; + + $scope.$on('menu-hidden', function() { + $scope.shown = false; + }); + } + }; + +}]); + +})(); +(function(){ + +/** + * @ngdoc directive + * @name ui.grid.directive:uiGridColumnMenu + * @element style + * @restrict A + * + * @description + * Allows us to interpolate expressions in ` + I am in a box. + + +
+ * mySortFn = function(a, b) { + * var nulls = $scope.gridApi.core.sortHandleNulls(a, b); + * if ( nulls !== null ){ + * return nulls; + * } else { + * // your code for sorting here + * }; + *+ * @param {object} a sort value a + * @param {object} b sort value b + * @returns {number} null if there were no nulls/undefineds, otherwise returns + * a sort value that should be passed back from the sort function + * + */ + self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls ); + + + /** + * @ngdoc function + * @name sortChanged + * @methodOf ui.grid.core.api:PublicApi + * @description The sort criteria on one or more columns has + * changed. Provides as parameters the grid and the output of + * getColumnSorting, which is an array of gridColumns + * that have sorting on them, sorted in priority order. + * + * @param {Grid} grid the grid + * @param {array} sortColumns an array of columns with + * sorts on them, in priority order + * + * @example + *
+ * gridApi.core.on.sortChanged( grid, sortColumns ); + *+ */ + self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); + + self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]); +}; + + /** + * @ngdoc function + * @name isRTL + * @methodOf ui.grid.class:Grid + * @description Returns true if grid is RightToLeft + */ + Grid.prototype.isRTL = function () { + return this.rtl; + }; + + + /** + * @ngdoc function + * @name registerColumnBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates columns from column definitions, the columnbuilders will be called to add + * additional properties to the column. + * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called + */ + Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) { + this.columnBuilders.push(columnBuilder); + }; + + /** + * @ngdoc function + * @name buildColumnDefsFromData + * @methodOf ui.grid.class:Grid + * @description Populates columnDefs from the provided data + * @param {function(colDef, col, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.buildColumnDefsFromData = function (dataRows){ + this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties); + }; + + /** + * @ngdoc function + * @name registerRowBuilder + * @methodOf ui.grid.class:Grid + * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add + * additional properties to the row. + * @param {function(row, gridOptions)} rowBuilder function to be called + */ + Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) { + this.rowBuilders.push(rowBuilder); + }; + + + /** + * @ngdoc function + * @name registerDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * + * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL + * @returns {string} uid of the callback, can be used to deregister it again + */ + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { + var uid = gridUtil.nextUid(); + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; + return uid; + }; + + /** + * @ngdoc function + * @name deregisterDataChangeCallback + * @methodOf ui.grid.class:Grid + * @description Delete the callback identified by the id. + * @param {string} uid the uid of the function that is to be deregistered + */ + Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) { + delete this.dataChangeCallbacks[uid]; + }; + + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }, this); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + + /** + * @ngdoc function + * @name columnRefreshCallback + * @methodOf ui.grid.class:Grid + * @description refreshes the grid when a column refresh + * is notified, which triggers handling of the visible flag + * @param {string} name column name + */ + Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){ + grid.refresh(); + }; + + + /** + * @ngdoc function + * @name getColumn + * @methodOf ui.grid.class:Grid + * @description returns a grid column for the column name + * @param {string} name column name + */ + Grid.prototype.getColumn = function getColumn(name) { + var columns = this.columns.filter(function (column) { + return column.colDef.name === name; + }); + return columns.length > 0 ? columns[0] : null; + }; + + /** + * @ngdoc function + * @name getColDef + * @methodOf ui.grid.class:Grid + * @description returns a grid colDef for the column name + * @param {string} name column.field + */ + Grid.prototype.getColDef = function getColDef(name) { + var colDefs = this.options.columnDefs.filter(function (colDef) { + return colDef.name === name; + }); + return colDefs.length > 0 ? colDefs[0] : null; + }; + + /** + * @ngdoc function + * @name assignTypes + * @methodOf ui.grid.class:Grid + * @description uses the first row of data to assign colDef.type for any types not defined. + */ + /** + * @ngdoc property + * @name type + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description the type of the column, used in sorting. If not provided then the + * grid will guess the type. Add this only if the grid guessing is not to your + * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for + * a list of values the grid knows about. + * + */ + Grid.prototype.assignTypes = function(){ + var self = this; + self.options.columnDefs.forEach(function (colDef, index) { + + //Assign colDef type if not specified + if (!colDef.type) { + var col = new GridColumn(colDef, index, self); + var firstRow = self.rows.length > 0 ? self.rows[0] : null; + if (firstRow) { + colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col)); + } + else { + gridUtil.logWarn('Unable to assign type from data, so defaulting to string'); + colDef.type = 'string'; + } + } + }); + }; + + /** + * @ngdoc function + * @name addRowHeaderColumn + * @methodOf ui.grid.class:Grid + * @description adds a row header column to the grid + * @param {object} column def + */ + Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) { + var self = this; + //self.createLeftContainer(); + var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self); + rowHeaderCol.isRowHeader = true; + if (self.isRTL()) { + self.createRightContainer(); + rowHeaderCol.renderContainer = 'right'; + } + else { + self.createLeftContainer(); + rowHeaderCol.renderContainer = 'left'; + } + + // relies on the default column builder being first in array, as it is instantiated + // as part of grid creation + self.columnBuilders[0](colDef,rowHeaderCol,self.options) + .then(function(){ + rowHeaderCol.enableFiltering = false; + rowHeaderCol.enableSorting = false; + rowHeaderCol.enableHiding = false; + self.rowHeaderColumns.push(rowHeaderCol); + self.buildColumns() + .then( function() { + self.preCompileCellTemplates(); + self.handleWindowResize(); + }); + }); + }; + + /** + * @ngdoc function + * @name buildColumns + * @methodOf ui.grid.class:Grid + * @description creates GridColumn objects from the columnDefinition. Calls each registered + * columnBuilder to further process the column + * @returns {Promise} a promise to load any needed column resources + */ + Grid.prototype.buildColumns = function buildColumns() { + // gridUtil.logDebug('buildColumns'); + var self = this; + var builderPromises = []; + var headerOffset = self.rowHeaderColumns.length; + var i; + + // Remove any columns for which a columnDef cannot be found + // Deliberately don't use forEach, as it doesn't like splice being called in the middle + // Also don't cache columns.length, as it will change during this operation + for (i = 0; i < self.columns.length; i++){ + if (!self.getColDef(self.columns[i].name)) { + self.columns.splice(i, 1); + i--; + } + } + + //add row header columns to the grid columns array _after_ columns without columnDefs have been removed + self.rowHeaderColumns.forEach(function (rowHeaderColumn) { + self.columns.unshift(rowHeaderColumn); + }); + + + // look at each column def, and update column properties to match. If the column def + // doesn't have a column, then splice in a new gridCol + self.options.columnDefs.forEach(function (colDef, index) { + self.preprocessColDef(colDef); + var col = self.getColumn(colDef.name); + + if (!col) { + col = new GridColumn(colDef, gridUtil.nextUid(), self); + self.columns.splice(index + headerOffset, 0, col); + } + else { + col.updateColumnDef(colDef); + } + + self.columnBuilders.forEach(function (builder) { + builderPromises.push(builder.call(self, colDef, col, self.options)); + }); + }); + + return $q.all(builderPromises); + }; + +/** + * @ngdoc function + * @name preCompileCellTemplates + * @methodOf ui.grid.class:Grid + * @description precompiles all cell templates + */ + Grid.prototype.preCompileCellTemplates = function() { + var self = this; + this.columns.forEach(function (col) { + var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col)); + html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)'); + + + var compiledElementFn = $compile(html); + col.compiledElementFn = compiledElementFn; + + if (col.compiledElementFnDefer) { + col.compiledElementFnDefer.resolve(col.compiledElementFn); + } + }); + }; + + /** + * @ngdoc function + * @name getGridQualifiedColField + * @methodOf ui.grid.class:Grid + * @description Returns the $parse-able accessor for a column within its $scope + * @param {GridColumn} col col object + */ + Grid.prototype.getQualifiedColField = function (col) { + return 'row.entity.' + gridUtil.preEval(col.field); + }; + + /** + * @ngdoc function + * @name createLeftContainer + * @methodOf ui.grid.class:Grid + * @description creates the left render container if it doesn't already exist + */ + Grid.prototype.createLeftContainer = function() { + if (!this.hasLeftContainer()) { + this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name createRightContainer + * @methodOf ui.grid.class:Grid + * @description creates the right render container if it doesn't already exist + */ + Grid.prototype.createRightContainer = function() { + if (!this.hasRightContainer()) { + this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true }); + } + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if leftContainer exists + */ + Grid.prototype.hasLeftContainer = function() { + return this.renderContainers.left !== undefined; + }; + + /** + * @ngdoc function + * @name hasLeftContainer + * @methodOf ui.grid.class:Grid + * @description returns true if rightContainer exists + */ + Grid.prototype.hasRightContainer = function() { + return this.renderContainers.right !== undefined; + }; + + + /** + * undocumented function + * @name preprocessColDef + * @methodOf ui.grid.class:Grid + * @description defaults the name property from field to maintain backwards compatibility with 2.x + * validates that name or field is present + */ + Grid.prototype.preprocessColDef = function preprocessColDef(colDef) { + if (!colDef.field && !colDef.name) { + throw new Error('colDef.name or colDef.field property is required'); + } + + //maintain backwards compatibility with 2.x + //field was required in 2.x. now name is required + if (colDef.name === undefined && colDef.field !== undefined) { + colDef.name = colDef.field; + } + + }; + + // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters + Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) { + var self = this; + + var t = []; + for (var i = 0; i < n.length; i++) { + var nV = nAccessor ? n[i][nAccessor] : n[i]; + + var found = false; + for (var j = 0; j < o.length; j++) { + var oV = oAccessor ? o[j][oAccessor] : o[j]; + if (self.options.rowEquality(nV, oV)) { + found = true; + break; + } + } + if (!found) { + t.push(nV); + } + } + + return t; + }; + + /** + * @ngdoc function + * @name getRow + * @methodOf ui.grid.class:Grid + * @description returns the GridRow that contains the rowEntity + * @param {object} rowEntity the gridOptions.data array element instance + */ + Grid.prototype.getRow = function getRow(rowEntity) { + var rows = this.rows.filter(function (row) { + return row.entity === rowEntity; + }); + return rows.length > 0 ? rows[0] : null; + }; + + + /** + * @ngdoc function + * @name modifyRows + * @methodOf ui.grid.class:Grid + * @description creates or removes GridRow objects from the newRawData array. Calls each registered + * rowBuilder to further process the row + * + * Rows are identified using the gridOptions.rowEquality function + */ + Grid.prototype.modifyRows = function modifyRows(newRawData) { + var self = this, + i, + rowhash, + found, + newRow; + if ((self.options.useExternalSorting || self.getColumnSorting().length === 0) && newRawData.length > 0) { + var oldRowHash = self.rowHashMap; + if (!oldRowHash) { + oldRowHash = {get: function(){return null;}}; + } + self.createRowHashMap(); + rowhash = self.rowHashMap; + var wasEmpty = self.rows.length === 0; + self.rows.length = 0; + for (i = 0; i < newRawData.length; i++) { + var newRawRow = newRawData[i]; + found = oldRowHash.get(newRawRow); + if (found) { + newRow = found.row; + } + else { + newRow = self.processRowBuilders(new GridRow(newRawRow, i, self)); + } + self.rows.push(newRow); + rowhash.put(newRawRow, { + i: i, + entity: newRawRow, + row:newRow + }); + } + //now that we have data, it is save to assign types to colDefs + if (wasEmpty) { + self.assignTypes(); + } + } else { + if (self.rows.length === 0 && newRawData.length > 0) { + if (self.options.enableRowHashing) { + if (!self.rowHashMap) { + self.createRowHashMap(); + } + + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + self.rowHashMap.put(newRow, { + i: i, + entity: newRow + }); + } + } + + self.addRows(newRawData); + //now that we have data, it is save to assign types to colDefs + self.assignTypes(); + } + else if (newRawData.length > 0) { + var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind; + + // If row hashing is turned on + if (self.options.enableRowHashing) { + // Array of new rows that haven't been found in the old rowset + unfoundNewRows = []; + // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist). + unfoundNewRowsToFind = []; + // Map of rows that have been found in the new rowset + var foundOldRows = {}; + // Array of old rows that have NOT been found in the new rowset + unfoundOldRows = []; + + // Create the row HashMap if it doesn't exist already + if (!self.rowHashMap) { + self.createRowHashMap(); + } + rowhash = self.rowHashMap; + + // Make sure every new row has a hash + for (i = 0; i < newRawData.length; i++) { + newRow = newRawData[i]; + + // Flag this row as needing to be manually found if it didn't come in with a $$hashKey + var mustFind = false; + if (!self.options.getRowIdentity(newRow)) { + mustFind = true; + } + + // See if the new row is already in the rowhash + found = rowhash.get(newRow); + // If so... + if (found) { + // See if it's already being used by as GridRow + if (found.row) { + // If so, mark this new row as being found + foundOldRows[self.options.rowIdentity(newRow)] = true; + } + } + else { + // Put the row in the hashmap with the index it corresponds to + rowhash.put(newRow, { + i: i, + entity: newRow + }); + + // This row has to be searched for manually in the old row set + if (mustFind) { + unfoundNewRowsToFind.push(newRow); + } + else { + unfoundNewRows.push(newRow); + } + } + } + + // Build the list of unfound old rows + for (i = 0; i < self.rows.length; i++) { + var row = self.rows[i]; + var hash = self.options.rowIdentity(row.entity); + if (!foundOldRows[hash]) { + unfoundOldRows.push(row); + } + } + } + + // Look for new rows + var newRows = unfoundNewRows || []; + + // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't + var unfoundNew = (unfoundNewRowsToFind || newRawData); + + // Search for real new rows in `unfoundNew` and concat them onto `newRows` + newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity')); + + self.addRows(newRows); + + var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData); + + for (i = 0; i < deletedRows.length; i++) { + if (self.options.enableRowHashing) { + self.rowHashMap.remove(deletedRows[i].entity); + } + + self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 ); + } + } + // Empty data set + else { + // Reset the row HashMap + self.createRowHashMap(); + + // Reset the rows length! + self.rows.length = 0; + } + } + + var p1 = $q.when(self.processRowsProcessors(self.rows)) + .then(function (renderableRows) { + return self.setVisibleRows(renderableRows); + }); + + var p2 = $q.when(self.processColumnsProcessors(self.columns)) + .then(function (renderableColumns) { + return self.setVisibleColumns(renderableColumns); + }); + + return $q.all([p1, p2]); + }; + + Grid.prototype.getDeletedRows = function(oldRows, newRows) { + var self = this; + + var olds = oldRows.filter(function (oldRow) { + return !newRows.some(function (newItem) { + return self.options.rowEquality(newItem, oldRow.entity); + }); + }); + // var olds = self.newInN(newRows, oldRows, null, 'entity'); + // dump('olds', olds); + return olds; + }; + + /** + * Private Undocumented Method + * @name addRows + * @methodOf ui.grid.class:Grid + * @description adds the newRawData array of rows to the grid and calls all registered + * rowBuilders. this keyword will reference the grid + */ + Grid.prototype.addRows = function addRows(newRawData) { + var self = this; + + var existingRowCount = self.rows.length; + for (var i = 0; i < newRawData.length; i++) { + var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self)); + + if (self.options.enableRowHashing) { + var found = self.rowHashMap.get(newRow.entity); + if (found) { + found.row = newRow; + } + } + + self.rows.push(newRow); + } + }; + + /** + * @ngdoc function + * @name processRowBuilders + * @methodOf ui.grid.class:Grid + * @description processes all RowBuilders for the gridRow + * @param {GridRow} gridRow reference to gridRow + * @returns {GridRow} the gridRow with all additional behavior added + */ + Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) { + var self = this; + + self.rowBuilders.forEach(function (builder) { + builder.call(self, gridRow, self.gridOptions); + }); + + return gridRow; + }; + + /** + * @ngdoc function + * @name registerStyleComputation + * @methodOf ui.grid.class:Grid + * @description registered a styleComputation function + * + * If the function returns a value it will be appended into the grid's `" + ); + + + $templateCache.put('ui-grid/uiGridCell', + "
Comments
+