<commit_before>/*!
 * Bootstrap's Gruntfile
 * http://getbootstrap.com
 * Copyright 2013-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */

module.exports = function (grunt) {
  'use strict';

  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

  RegExp.quote = function (string) {
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };

  var fs = require('fs');
  var path = require('path');
  var BsLessdocParser = require('./grunt/bs-lessdoc-parser.js');
  var getLessVarsData = function () {
    var filePath = path.join(__dirname, 'less/variables.less');
    var fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
    var parser = new BsLessdocParser(fileContent);
    return { sections: parser.parseFile() };
  };
  var generateRawFiles = require('./grunt/bs-raw-files-generator.js');

  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*!\n' +
            ' * TODC Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
            ' * Licensed under <%= pkg.license.type %> (<%= pkg.license.url %>)\n' +
            ' */\n',
    // NOTE: This jqueryCheck/jqueryVersionCheck code is duplicated in customizer.js;
    //       if making changes here, be sure to update the other copy too.
    jqueryCheck: [
      'if (typeof jQuery === \'undefined\') {',
      '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')',
      '}\n'
    ].join('\n'),
    jqueryVersionCheck: [
      '+function ($) {',
      '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')',
      '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {',
      '    throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery version 1.9.1 or higher\')',
      '  }',
      '}(jQuery);\n\n'
    ].join('\n'),

    // Task configuration.
    clean: {
      dist: 'dist',
      docs: 'docs/dist'
    },

    jshint: {
      options: {
        jshintrc: 'js/.jshintrc'
      },
      grunt: {
        options: {
          jshintrc: 'grunt/.jshintrc'
        },
        src: ['Gruntfile.js', 'grunt/*.js']
      },
      assets: {
        src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
      }
    },

    jscs: {
      options: {
        config: 'js/.jscsrc'
      },
      grunt: {
        src: '<%= jshint.grunt.src %>'
      },
      assets: {
        options: {
          requireCamelCaseOrUpperCaseIdentifiers: null
        },
        src: '<%= jshint.assets.src %>'
      }
    },

    uglify: {
      options: {
        preserveComments: 'some'
      },
      core: {
      },
      docsJs: {
        // NOTE: This src list is duplicated in footer.html; if making changes here, be sure to update the other copy too.
        src: [
          'docs/assets/js/vendor/select2.js',
          'docs/assets/js/vendor/holder.js',
          'docs/assets/js/vendor/ZeroClipboard.min.js',
          'docs/assets/js/src/application.js'
        ],
        dest: 'docs/assets/js/docs.min.js'
      }
    },

    less: {
      compileCore: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
        },
        src: 'less/todc-bootstrap.less',
        dest: 'dist/css/<%= pkg.name %>.css'
      }
    },

    autoprefixer: {
      options: {
        browsers: [
          'Android 2.3',
          'Android >= 4',
          'Chrome >= 20',
          'Firefox >= 24', // Firefox 24 is the latest ESR
          'Explorer >= 8',
          'iOS >= 6',
          'Opera >= 12',
          'Safari >= 6'
        ]
      },
      core: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>.css'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      }
    },

    csslint: {
      options: {
        csslintrc: 'less/.csslintrc'
      },
      dist: [
        'dist/css/todc-bootstrap.css'
      ],
      examples: [
        'docs/examples/**/*.css'
      ],
      docs: {
        options: {
          ids: false,
          'overqualified-elements': false
        },
        src: 'docs/assets/css/src/docs.css'
      }
    },

    cssmin: {
      options: {
        compatibility: 'ie8',
        keepSpecialComments: '*',
        noAdvanced: true

      },
      minifyCore: {
        src: 'dist/css/<%= pkg.name %>.css',
        dest: 'dist/css/<%= pkg.name %>.min.css'
      },
      docs: {
        src: [
          'docs/assets/css/src/docs.css',
          'docs/assets/css/src/pygments-manni.css',
          'docs/assets/css/src/select2.css'
        ],
        dest: 'docs/assets/css/docs.min.css'
      }
    },

    usebanner: {
      options: {
        position: 'top',
        banner: '<%= banner %>'
      },
      files: {
        src: 'dist/css/*.css'
      }
    },

    csscomb: {
      options: {
        config: 'less/.csscomb.json'
      },
      dist: {
        expand: true,
        cwd: 'dist/css/',
        src: ['*.css', '!*.min.css'],
        dest: 'dist/css/'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css',
        dest: 'docs/assets/css/src/docs.css'
      }
    },

    copy: {
      bootstrap: {
        expand: true,
        flatten: false,
        cwd: '<%= bootstrapDir %>/dist',
        src: '*/*',
        dest: 'dist'
      },
      images: {
        src: 'img/*',
        dest: 'dist/'
      },
      docs: {
        src: 'dist/*/*',
        dest: 'docs/'
      }
    },

    compress: {
      main: {
        options: {
          archive: 'dist/<%= pkg.name %>-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          pretty: true
        },
        expand: true,
        cwd: 'dist',
        src: '**',
        dest: 'todc-bootstrap/'
      }
    },

    jekyll: {
      docs: {}
    },

    jade: {
      options: {
        pretty: true,
        data: getLessVarsData
      },
      customizerVars: {
        src: 'docs/_jade/customizer-variables.jade',
        dest: 'docs/_includes/customizer-variables.html'
      },
      customizerNav: {
        src: 'docs/_jade/customizer-nav.jade',
        dest: 'docs/_includes/nav/customize.html'
      }
    },

    validation: {
      options: {
        charset: 'utf-8',
        doctype: 'HTML5',
        failHard: true,
        reset: true,
        relaxerror: [
          'Bad value X-UA-Compatible for attribute http-equiv on element meta.',
          'Element img is missing required attribute src.',
          'Attribute autocomplete not allowed on element input at this point.',
          'Attribute autocomplete not allowed on element button at this point.'
        ]
      },
      files: {
        src: '_gh_pages/**/*.html'
      }
    },

    watch: {
      less: {
        files: 'less/**/*.less',
        tasks: 'less'
      }
    },

    // checkout-bootstrap task
    shell: {
      gitclone: {
        command: 'git clone <%= bootstrapGit %> <%= bootstrapDir %>'
      },
      gitcheckout: {
        command: 'git checkout tags/<%= bootstrapVersion %>',
        options: {
          execOptions: {
            cwd: '<%= bootstrapDir %>'
          }
        }
      }
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver');
          return old ? RegExp.quote(old) : old;
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
    }
  });

  // These plugins provide necessary tasks.
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
  require('time-grunt')(grunt);

  // Clone bootstrap and checkout the appropriate tag task.
  grunt.registerTask('checkout-bootstrap', 'shell');

  // Docs HTML validation task
  grunt.registerTask('validate-html', ['jekyll', 'validation']);

  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };

  // Test task.
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
  if (runSubset('core')) {
     testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'csslint:dist', 'test-js', 'docs']);
  }
  // Skip HTML validation if running a different subset of the test suite
  if (runSubset('validate-html') &&
      // Skip HTML5 validator on Travis when [skip validator] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
    testSubtasks.push('validate-html');
  }
  grunt.registerTask('test', testSubtasks);
  grunt.registerTask('test-js', ['jshint:grunt', 'jscs:grunt']);

  // JS distribution task.
  grunt.registerTask('dist-js', 'uglify:core');

  // CSS distribution task.
  grunt.registerTask('less-compile', ['less:compileCore']);
  grunt.registerTask('dist-css', ['less-compile', 'autoprefixer:core', 'usebanner', 'csscomb:dist', 'cssmin:minifyCore']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:bootstrap', 'copy:images', 'dist-js']);

  // Default task.
  grunt.registerTask('default', ['clean:dist', 'copy:bootstrap', 'copy:images', 'test']);

  // Version numbering task.
  // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  // This can be overzealous, so its changes should always be manually reviewed!
  grunt.registerTask('change-version-number', 'sed');

  // task for building customizer
  grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']);
  grunt.registerTask('build-customizer-html', 'jade');
  grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () {
    var banner = grunt.template.process('<%= banner %>');
    generateRawFiles(grunt, banner);
  });

  // Docs task.
  grunt.registerTask('docs-css', ['autoprefixer:docs', 'autoprefixer:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
  grunt.registerTask('lint-docs-css', ['csslint:docs', 'csslint:examples']);
  grunt.registerTask('docs-js', ['uglify:docsJs']);
  grunt.registerTask('lint-docs-js', ['jshint:assets', 'jscs:assets']);
  grunt.registerTask('docs', ['docs-css', 'lint-docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs']);
};
<commit_msg>put back the bootstrap variables<commit_after>/*!
 * Bootstrap's Gruntfile
 * http://getbootstrap.com
 * Copyright 2013-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */

module.exports = function (grunt) {
  'use strict';

  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

  RegExp.quote = function (string) {
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };

  var fs = require('fs');
  var path = require('path');
  var BsLessdocParser = require('./grunt/bs-lessdoc-parser.js');
  var getLessVarsData = function () {
    var filePath = path.join(__dirname, 'less/variables.less');
    var fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
    var parser = new BsLessdocParser(fileContent);
    return { sections: parser.parseFile() };
  };
  var generateRawFiles = require('./grunt/bs-raw-files-generator.js');

  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*!\n' +
            ' * TODC Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
            ' * Licensed under <%= pkg.license.type %> (<%= pkg.license.url %>)\n' +
            ' */\n',
    // NOTE: This jqueryCheck/jqueryVersionCheck code is duplicated in customizer.js;
    //       if making changes here, be sure to update the other copy too.
    jqueryCheck: [
      'if (typeof jQuery === \'undefined\') {',
      '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')',
      '}\n'
    ].join('\n'),
    jqueryVersionCheck: [
      '+function ($) {',
      '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')',
      '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {',
      '    throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery version 1.9.1 or higher\')',
      '  }',
      '}(jQuery);\n\n'
    ].join('\n'),

    // Bootstrap variables
    bootstrapDir: 'bootstrap',
    bootstrapGit: 'https://github.com/twbs/bootstrap.git',
    bootstrapVersion: 'v3.2.0',

    // Task configuration.
    clean: {
      dist: 'dist',
      docs: 'docs/dist'
    },

    jshint: {
      options: {
        jshintrc: 'js/.jshintrc'
      },
      grunt: {
        options: {
          jshintrc: 'grunt/.jshintrc'
        },
        src: ['Gruntfile.js', 'grunt/*.js']
      },
      assets: {
        src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
      }
    },

    jscs: {
      options: {
        config: 'js/.jscsrc'
      },
      grunt: {
        src: '<%= jshint.grunt.src %>'
      },
      assets: {
        options: {
          requireCamelCaseOrUpperCaseIdentifiers: null
        },
        src: '<%= jshint.assets.src %>'
      }
    },

    uglify: {
      options: {
        preserveComments: 'some'
      },
      core: {
      },
      docsJs: {
        // NOTE: This src list is duplicated in footer.html; if making changes here, be sure to update the other copy too.
        src: [
          'docs/assets/js/vendor/select2.js',
          'docs/assets/js/vendor/holder.js',
          'docs/assets/js/vendor/ZeroClipboard.min.js',
          'docs/assets/js/src/application.js'
        ],
        dest: 'docs/assets/js/docs.min.js'
      }
    },

    less: {
      compileCore: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
        },
        src: 'less/todc-bootstrap.less',
        dest: 'dist/css/<%= pkg.name %>.css'
      }
    },

    autoprefixer: {
      options: {
        browsers: [
          'Android 2.3',
          'Android >= 4',
          'Chrome >= 20',
          'Firefox >= 24', // Firefox 24 is the latest ESR
          'Explorer >= 8',
          'iOS >= 6',
          'Opera >= 12',
          'Safari >= 6'
        ]
      },
      core: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>.css'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      }
    },

    csslint: {
      options: {
        csslintrc: 'less/.csslintrc'
      },
      dist: [
        'dist/css/todc-bootstrap.css'
      ],
      examples: [
        'docs/examples/**/*.css'
      ],
      docs: {
        options: {
          ids: false,
          'overqualified-elements': false
        },
        src: 'docs/assets/css/src/docs.css'
      }
    },

    cssmin: {
      options: {
        compatibility: 'ie8',
        keepSpecialComments: '*',
        noAdvanced: true

      },
      minifyCore: {
        src: 'dist/css/<%= pkg.name %>.css',
        dest: 'dist/css/<%= pkg.name %>.min.css'
      },
      docs: {
        src: [
          'docs/assets/css/src/docs.css',
          'docs/assets/css/src/pygments-manni.css',
          'docs/assets/css/src/select2.css'
        ],
        dest: 'docs/assets/css/docs.min.css'
      }
    },

    usebanner: {
      options: {
        position: 'top',
        banner: '<%= banner %>'
      },
      files: {
        src: 'dist/css/*.css'
      }
    },

    csscomb: {
      options: {
        config: 'less/.csscomb.json'
      },
      dist: {
        expand: true,
        cwd: 'dist/css/',
        src: ['*.css', '!*.min.css'],
        dest: 'dist/css/'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css',
        dest: 'docs/assets/css/src/docs.css'
      }
    },

    copy: {
      bootstrap: {
        expand: true,
        flatten: false,
        cwd: '<%= bootstrapDir %>/dist',
        src: '*/*',
        dest: 'dist'
      },
      images: {
        src: 'img/*',
        dest: 'dist/'
      },
      docs: {
        src: 'dist/*/*',
        dest: 'docs/'
      }
    },

    compress: {
      main: {
        options: {
          archive: 'dist/<%= pkg.name %>-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          pretty: true
        },
        expand: true,
        cwd: 'dist',
        src: '**',
        dest: 'todc-bootstrap/'
      }
    },

    jekyll: {
      docs: {}
    },

    jade: {
      options: {
        pretty: true,
        data: getLessVarsData
      },
      customizerVars: {
        src: 'docs/_jade/customizer-variables.jade',
        dest: 'docs/_includes/customizer-variables.html'
      },
      customizerNav: {
        src: 'docs/_jade/customizer-nav.jade',
        dest: 'docs/_includes/nav/customize.html'
      }
    },

    validation: {
      options: {
        charset: 'utf-8',
        doctype: 'HTML5',
        failHard: true,
        reset: true,
        relaxerror: [
          'Bad value X-UA-Compatible for attribute http-equiv on element meta.',
          'Element img is missing required attribute src.',
          'Attribute autocomplete not allowed on element input at this point.',
          'Attribute autocomplete not allowed on element button at this point.'
        ]
      },
      files: {
        src: '_gh_pages/**/*.html'
      }
    },

    watch: {
      less: {
        files: 'less/**/*.less',
        tasks: 'less'
      }
    },

    // checkout-bootstrap task
    shell: {
      gitclone: {
        command: 'git clone <%= bootstrapGit %> <%= bootstrapDir %>'
      },
      gitcheckout: {
        command: 'git checkout tags/<%= bootstrapVersion %>',
        options: {
          execOptions: {
            cwd: '<%= bootstrapDir %>'
          }
        }
      }
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver');
          return old ? RegExp.quote(old) : old;
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
    }
  });

  // These plugins provide necessary tasks.
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
  require('time-grunt')(grunt);

  // Clone bootstrap and checkout the appropriate tag task.
  grunt.registerTask('checkout-bootstrap', 'shell');

  // Docs HTML validation task
  grunt.registerTask('validate-html', ['jekyll', 'validation']);

  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };

  // Test task.
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
  if (runSubset('core')) {
     testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'csslint:dist', 'test-js', 'docs']);
  }
  // Skip HTML validation if running a different subset of the test suite
  if (runSubset('validate-html') &&
      // Skip HTML5 validator on Travis when [skip validator] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
    testSubtasks.push('validate-html');
  }
  grunt.registerTask('test', testSubtasks);
  grunt.registerTask('test-js', ['jshint:grunt', 'jscs:grunt']);

  // JS distribution task.
  grunt.registerTask('dist-js', 'uglify:core');

  // CSS distribution task.
  grunt.registerTask('less-compile', ['less:compileCore']);
  grunt.registerTask('dist-css', ['less-compile', 'autoprefixer:core', 'usebanner', 'csscomb:dist', 'cssmin:minifyCore']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:bootstrap', 'copy:images', 'dist-js']);

  // Default task.
  grunt.registerTask('default', ['clean:dist', 'copy:bootstrap', 'copy:images', 'test']);

  // Version numbering task.
  // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  // This can be overzealous, so its changes should always be manually reviewed!
  grunt.registerTask('change-version-number', 'sed');

  // task for building customizer
  grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']);
  grunt.registerTask('build-customizer-html', 'jade');
  grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () {
    var banner = grunt.template.process('<%= banner %>');
    generateRawFiles(grunt, banner);
  });

  // Docs task.
  grunt.registerTask('docs-css', ['autoprefixer:docs', 'autoprefixer:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
  grunt.registerTask('lint-docs-css', ['csslint:docs', 'csslint:examples']);
  grunt.registerTask('docs-js', ['uglify:docsJs']);
  grunt.registerTask('lint-docs-js', ['jshint:assets', 'jscs:assets']);
  grunt.registerTask('docs', ['docs-css', 'lint-docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs']);
};
<|endoftext|>


<commit_before>var markdown = require('node-markdown').Markdown;

module.exports = function(grunt) {

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-html2js');
  grunt.loadNpmTasks('grunt-karma');
  grunt.loadNpmTasks('grunt-conventional-changelog');
  grunt.loadNpmTasks('grunt-ngdocs');

  // Project configuration.
  grunt.util.linefeed = '\n';

  grunt.initConfig({
    ngversion: '1.2.10',
    bsversion: '3.0.3',
    modules: [],//to be filled in by build task
    pkg: grunt.file.readJSON('package.json'),
    dist: 'dist',
    filename: 'ui-bootstrap',
    filenamecustom: '<%= filename %>-custom',
    meta: {
      modules: 'angular.module("ui.bootstrap", [<%= srcModules %>]);',
      tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);',
      all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);',
      banner: ['/*',
               ' * <%= pkg.name %>',
               ' * <%= pkg.homepage %>\n',
               ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
               ' * License: <%= pkg.license %>',
               ' */\n'].join('\n')
    },
    delta: {
      docs: {
        files: ['misc/demo/index.html'],
        tasks: ['after-test']
      },
      html: {
        files: ['template/**/*.html'],
        tasks: ['html2js', 'karma:watch:run']
      },
      js: {
        files: ['src/**/*.js'],
        //we don't need to jshint here, it slows down everything else
        tasks: ['karma:watch:run']
      }
    },
    concat: {
      dist: {
        options: {
          banner: '<%= meta.banner %><%= meta.modules %>\n'
        },
        src: [], //src filled in by build task
        dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
      },
      dist_tpls: {
        options: {
          banner: '<%= meta.banner %><%= meta.all %>\n<%= meta.tplmodules %>\n'
        },
        src: [], //src filled in by build task
        dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'
      }
    },
    copy: {
      demohtml: {
        options: {
          //process html files with gruntfile config
          processContent: grunt.template.process
        },
        files: [{
          expand: true,
          src: ['**/*.html'],
          cwd: 'misc/demo/',
          dest: 'dist/'
        }]
      },
      demoassets: {
        files: [{
          expand: true,
          //Don't re-copy html files, we process those
          src: ['**/**/*', '!**/*.html'],
          cwd: 'misc/demo',
          dest: 'dist/'
        }]
      }
    },
    uglify: {
      options: {
        banner: '<%= meta.banner %>'
      },
      dist:{
        src:['<%= concat.dist.dest %>'],
        dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
      },
      dist_tpls:{
        src:['<%= concat.dist_tpls.dest %>'],
        dest:'<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.min.js'
      }
    },
    html2js: {
      dist: {
        options: {
          module: null, // no bundle module for all the html2js templates
          base: '.'
        },
        files: [{
          expand: true,
          src: ['template/**/*.html'],
          ext: '.html.js'
        }]
      }
    },
    jshint: {
      files: ['Gruntfile.js','src/**/*.js'],
      options: {
        jshintrc: '.jshintrc'
      }
    },
    karma: {
      options: {
        configFile: 'karma.conf.js'
      },
      watch: {
        background: true
      },
      continuous: {
        singleRun: true
      },
      jenkins: {
        singleRun: true,
        colors: false,
        reporters: ['dots', 'junit'],
        browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Opera', '/Users/jenkins/bin/safari.sh', '/Users/jenkins/bin/ie9.sh', '/Users/jenkins/bin/ie10.sh', '/Users/jenkins/bin/ie11.sh']
      },
      travis: {
        singleRun: true,
        browsers: ['Firefox']
      },
      coverage: {
        preprocessors: {
          'src/*/*.js': 'coverage'
        },
        reporters: ['progress', 'coverage']
      }
    },
    changelog: {
      options: {
        dest: 'CHANGELOG.md',
        templateFile: 'misc/changelog.tpl.md',
        github: 'angular-ui/bootstrap'
      }
    },
    shell: {
      //We use %version% and evluate it at run-time, because <%= pkg.version %>
      //is only evaluated once
      'release-prepare': [
        'grunt before-test after-test',
        'grunt version', //remove "-SNAPSHOT"
        'grunt changelog'
      ],
      'release-complete': [
        'git commit CHANGELOG.md package.json -m "chore(release): v%version%"',
        'git tag %version%'
      ],
      'release-start': [
        'grunt version:minor:"SNAPSHOT"',
        'git commit package.json -m "chore(release): Starting v%version%"'
      ]
    },
    ngdocs: {
      options: {
        dest: 'dist/docs',
        scripts: [
          'angular.js',
          '<%= concat.dist_tpls.dest %>'
        ],
        styles: [
          'docs/css/style.css'
        ],
        navTemplate: 'docs/nav.html',
        title: 'ui-bootstrap',
        html5Mode: false
      },
      api: {
        src: ['src/**/*.js', 'src/**/*.ngdoc'],
        title: 'API Documentation'
      }
    }
  });

  //register before and after test tasks so we've don't have to change cli
  //options on the goole's CI server
  grunt.registerTask('before-test', ['enforce', 'jshint', 'html2js']);
  grunt.registerTask('after-test', ['build', 'copy']);

  //Rename our watch task to 'delta', then make actual 'watch'
  //task build things, then start test server
  grunt.renameTask('watch', 'delta');
  grunt.registerTask('watch', ['before-test', 'after-test', 'karma:watch', 'delta']);

  // Default task.
  grunt.registerTask('default', ['before-test', 'test', 'after-test']);

  grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', function() {
    if (!grunt.file.exists('.git/hooks/commit-msg')) {
      grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg');
      require('fs').chmodSync('.git/hooks/commit-msg', '0755');
    }
  });

  //Common ui.bootstrap module containing all modules for src and templates
  //findModule: Adds a given module to config
  var foundModules = {};
  function findModule(name) {
    if (foundModules[name]) { return; }
    foundModules[name] = true;

    function breakup(text, separator) {
      return text.replace(/[A-Z]/g, function (match) {
        return separator + match;
      });
    }
    function ucwords(text) {
      return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
        return $1.toUpperCase();
      });
    }
    function enquote(str) {
      return '"' + str + '"';
    }

    var module = {
      name: name,
      moduleName: enquote('ui.bootstrap.' + name),
      displayName: ucwords(breakup(name, ' ')),
      srcFiles: grunt.file.expand('src/'+name+'/*.js'),
      tplFiles: grunt.file.expand('template/'+name+'/*.html'),
      tpljsFiles: grunt.file.expand('template/'+name+'/*.html.js'),
      tplModules: grunt.file.expand('template/'+name+'/*.html').map(enquote),
      dependencies: dependenciesForModule(name),
      docs: {
        md: grunt.file.expand('src/'+name+'/docs/*.md')
          .map(grunt.file.read).map(markdown).join('\n'),
        js: grunt.file.expand('src/'+name+'/docs/*.js')
          .map(grunt.file.read).join('\n'),
        html: grunt.file.expand('src/'+name+'/docs/*.html')
          .map(grunt.file.read).join('\n')
      }
    };
    module.dependencies.forEach(findModule);
    grunt.config('modules', grunt.config('modules').concat(module));
  }

  function dependenciesForModule(name) {
    var deps = [];
    grunt.file.expand('src/' + name + '/*.js')
    .map(grunt.file.read)
    .forEach(function(contents) {
      //Strategy: find where module is declared,
      //and from there get everything inside the [] and split them by comma
      var moduleDeclIndex = contents.indexOf('angular.module(');
      var depArrayStart = contents.indexOf('[', moduleDeclIndex);
      var depArrayEnd = contents.indexOf(']', depArrayStart);
      var dependencies = contents.substring(depArrayStart + 1, depArrayEnd);
      dependencies.split(',').forEach(function(dep) {
        if (dep.indexOf('ui.bootstrap.') > -1) {
          var depName = dep.trim().replace('ui.bootstrap.','').replace(/['"]/g,'');
          if (deps.indexOf(depName) < 0) {
            deps.push(depName);
            //Get dependencies for this new dependency
            deps = deps.concat(dependenciesForModule(depName));
          }
        }
      });
    });
    return deps;
  }

  grunt.registerTask('dist', 'Override dist directory', function() {
    var dir = this.args[0];
    if (dir) { grunt.config('dist', dir); }
  });

  grunt.registerTask('build', 'Create bootstrap build files', function() {
    var _ = grunt.util._;

    //If arguments define what modules to build, build those. Else, everything
    if (this.args.length) {
      this.args.forEach(findModule);
      grunt.config('filename', grunt.config('filenamecustom'));
    } else {
      grunt.file.expand({
        filter: 'isDirectory', cwd: '.'
      }, 'src/*').forEach(function(dir) {
        findModule(dir.split('/')[1]);
      });
    }

    var modules = grunt.config('modules');
    grunt.config('srcModules', _.pluck(modules, 'moduleName'));
    grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} ));
    grunt.config('demoModules', modules
      .filter(function(module) {
        return module.docs.md && module.docs.js && module.docs.html;
      })
      .sort(function(a, b) {
        if (a.name < b.name) { return -1; }
        if (a.name > b.name) { return 1; }
        return 0;
      })
    );

    var srcFiles = _.pluck(modules, 'srcFiles');
    var tpljsFiles = _.pluck(modules, 'tpljsFiles');
    //Set the concat task to concatenate the given src modules
    grunt.config('concat.dist.src', grunt.config('concat.dist.src')
                 .concat(srcFiles));
    //Set the concat-with-templates task to concat the given src & tpl modules
    grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
                 .concat(srcFiles).concat(tpljsFiles));

    grunt.task.run(['concat', 'uglify']);
  });

  grunt.registerTask('test', 'Run tests on singleRun karma server', function () {
    //this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI
    //we need to take settings for each one into account
    if (process.env.TRAVIS) {
      grunt.task.run('karma:travis');
    } else {
      var isToRunJenkinsTask = !!this.args.length;
      if(grunt.option('coverage')) {
        var karmaOptions = grunt.config.get('karma.options'),
          coverageOpts = grunt.config.get('karma.coverage');
        grunt.util._.extend(karmaOptions, coverageOpts);
        grunt.config.set('karma.options', karmaOptions);
      }
      grunt.task.run(this.args.length ? 'karma:jenkins' : 'karma:continuous');
    }
  });

  function setVersion(type, suffix) {
    var file = 'package.json';
    var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;
    var contents = grunt.file.read(file);
    var version;
    contents = contents.replace(VERSION_REGEX, function(match, left, center) {
      version = center;
      if (type) {
        version = require('semver').inc(version, type);
      }
      //semver.inc strips our suffix if it existed
      if (suffix) {
        version += '-' + suffix;
      }
      return left + version + '"';
    });
    grunt.log.ok('Version set to ' + version.cyan);
    grunt.file.write(file, contents);
    return version;
  }

  grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', function() {
    setVersion(this.args[0], this.args[1]);
  });

  grunt.registerMultiTask('shell', 'run shell commands', function() {
    var self = this;
    var sh = require('shelljs');
    self.data.forEach(function(cmd) {
      cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version);
      grunt.log.ok(cmd);
      var result = sh.exec(cmd,{silent:true});
      if (result.code !== 0) {
        grunt.fatal(result.output);
      }
    });
  });

  return grunt;
};
<commit_msg>chore(ci): switch off IE testing on TravisCI<commit_after>var markdown = require('node-markdown').Markdown;

module.exports = function(grunt) {

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-html2js');
  grunt.loadNpmTasks('grunt-karma');
  grunt.loadNpmTasks('grunt-conventional-changelog');
  grunt.loadNpmTasks('grunt-ngdocs');

  // Project configuration.
  grunt.util.linefeed = '\n';

  grunt.initConfig({
    ngversion: '1.2.10',
    bsversion: '3.0.3',
    modules: [],//to be filled in by build task
    pkg: grunt.file.readJSON('package.json'),
    dist: 'dist',
    filename: 'ui-bootstrap',
    filenamecustom: '<%= filename %>-custom',
    meta: {
      modules: 'angular.module("ui.bootstrap", [<%= srcModules %>]);',
      tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);',
      all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);',
      banner: ['/*',
               ' * <%= pkg.name %>',
               ' * <%= pkg.homepage %>\n',
               ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
               ' * License: <%= pkg.license %>',
               ' */\n'].join('\n')
    },
    delta: {
      docs: {
        files: ['misc/demo/index.html'],
        tasks: ['after-test']
      },
      html: {
        files: ['template/**/*.html'],
        tasks: ['html2js', 'karma:watch:run']
      },
      js: {
        files: ['src/**/*.js'],
        //we don't need to jshint here, it slows down everything else
        tasks: ['karma:watch:run']
      }
    },
    concat: {
      dist: {
        options: {
          banner: '<%= meta.banner %><%= meta.modules %>\n'
        },
        src: [], //src filled in by build task
        dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
      },
      dist_tpls: {
        options: {
          banner: '<%= meta.banner %><%= meta.all %>\n<%= meta.tplmodules %>\n'
        },
        src: [], //src filled in by build task
        dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'
      }
    },
    copy: {
      demohtml: {
        options: {
          //process html files with gruntfile config
          processContent: grunt.template.process
        },
        files: [{
          expand: true,
          src: ['**/*.html'],
          cwd: 'misc/demo/',
          dest: 'dist/'
        }]
      },
      demoassets: {
        files: [{
          expand: true,
          //Don't re-copy html files, we process those
          src: ['**/**/*', '!**/*.html'],
          cwd: 'misc/demo',
          dest: 'dist/'
        }]
      }
    },
    uglify: {
      options: {
        banner: '<%= meta.banner %>'
      },
      dist:{
        src:['<%= concat.dist.dest %>'],
        dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
      },
      dist_tpls:{
        src:['<%= concat.dist_tpls.dest %>'],
        dest:'<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.min.js'
      }
    },
    html2js: {
      dist: {
        options: {
          module: null, // no bundle module for all the html2js templates
          base: '.'
        },
        files: [{
          expand: true,
          src: ['template/**/*.html'],
          ext: '.html.js'
        }]
      }
    },
    jshint: {
      files: ['Gruntfile.js','src/**/*.js'],
      options: {
        jshintrc: '.jshintrc'
      }
    },
    karma: {
      options: {
        configFile: 'karma.conf.js'
      },
      watch: {
        background: true
      },
      continuous: {
        singleRun: true
      },
      jenkins: {
        singleRun: true,
        colors: false,
        reporters: ['dots', 'junit'],
        browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Opera', '/Users/jenkins/bin/safari.sh']
      },
      travis: {
        singleRun: true,
        browsers: ['Firefox']
      },
      coverage: {
        preprocessors: {
          'src/*/*.js': 'coverage'
        },
        reporters: ['progress', 'coverage']
      }
    },
    changelog: {
      options: {
        dest: 'CHANGELOG.md',
        templateFile: 'misc/changelog.tpl.md',
        github: 'angular-ui/bootstrap'
      }
    },
    shell: {
      //We use %version% and evluate it at run-time, because <%= pkg.version %>
      //is only evaluated once
      'release-prepare': [
        'grunt before-test after-test',
        'grunt version', //remove "-SNAPSHOT"
        'grunt changelog'
      ],
      'release-complete': [
        'git commit CHANGELOG.md package.json -m "chore(release): v%version%"',
        'git tag %version%'
      ],
      'release-start': [
        'grunt version:minor:"SNAPSHOT"',
        'git commit package.json -m "chore(release): Starting v%version%"'
      ]
    },
    ngdocs: {
      options: {
        dest: 'dist/docs',
        scripts: [
          'angular.js',
          '<%= concat.dist_tpls.dest %>'
        ],
        styles: [
          'docs/css/style.css'
        ],
        navTemplate: 'docs/nav.html',
        title: 'ui-bootstrap',
        html5Mode: false
      },
      api: {
        src: ['src/**/*.js', 'src/**/*.ngdoc'],
        title: 'API Documentation'
      }
    }
  });

  //register before and after test tasks so we've don't have to change cli
  //options on the goole's CI server
  grunt.registerTask('before-test', ['enforce', 'jshint', 'html2js']);
  grunt.registerTask('after-test', ['build', 'copy']);

  //Rename our watch task to 'delta', then make actual 'watch'
  //task build things, then start test server
  grunt.renameTask('watch', 'delta');
  grunt.registerTask('watch', ['before-test', 'after-test', 'karma:watch', 'delta']);

  // Default task.
  grunt.registerTask('default', ['before-test', 'test', 'after-test']);

  grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', function() {
    if (!grunt.file.exists('.git/hooks/commit-msg')) {
      grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg');
      require('fs').chmodSync('.git/hooks/commit-msg', '0755');
    }
  });

  //Common ui.bootstrap module containing all modules for src and templates
  //findModule: Adds a given module to config
  var foundModules = {};
  function findModule(name) {
    if (foundModules[name]) { return; }
    foundModules[name] = true;

    function breakup(text, separator) {
      return text.replace(/[A-Z]/g, function (match) {
        return separator + match;
      });
    }
    function ucwords(text) {
      return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
        return $1.toUpperCase();
      });
    }
    function enquote(str) {
      return '"' + str + '"';
    }

    var module = {
      name: name,
      moduleName: enquote('ui.bootstrap.' + name),
      displayName: ucwords(breakup(name, ' ')),
      srcFiles: grunt.file.expand('src/'+name+'/*.js'),
      tplFiles: grunt.file.expand('template/'+name+'/*.html'),
      tpljsFiles: grunt.file.expand('template/'+name+'/*.html.js'),
      tplModules: grunt.file.expand('template/'+name+'/*.html').map(enquote),
      dependencies: dependenciesForModule(name),
      docs: {
        md: grunt.file.expand('src/'+name+'/docs/*.md')
          .map(grunt.file.read).map(markdown).join('\n'),
        js: grunt.file.expand('src/'+name+'/docs/*.js')
          .map(grunt.file.read).join('\n'),
        html: grunt.file.expand('src/'+name+'/docs/*.html')
          .map(grunt.file.read).join('\n')
      }
    };
    module.dependencies.forEach(findModule);
    grunt.config('modules', grunt.config('modules').concat(module));
  }

  function dependenciesForModule(name) {
    var deps = [];
    grunt.file.expand('src/' + name + '/*.js')
    .map(grunt.file.read)
    .forEach(function(contents) {
      //Strategy: find where module is declared,
      //and from there get everything inside the [] and split them by comma
      var moduleDeclIndex = contents.indexOf('angular.module(');
      var depArrayStart = contents.indexOf('[', moduleDeclIndex);
      var depArrayEnd = contents.indexOf(']', depArrayStart);
      var dependencies = contents.substring(depArrayStart + 1, depArrayEnd);
      dependencies.split(',').forEach(function(dep) {
        if (dep.indexOf('ui.bootstrap.') > -1) {
          var depName = dep.trim().replace('ui.bootstrap.','').replace(/['"]/g,'');
          if (deps.indexOf(depName) < 0) {
            deps.push(depName);
            //Get dependencies for this new dependency
            deps = deps.concat(dependenciesForModule(depName));
          }
        }
      });
    });
    return deps;
  }

  grunt.registerTask('dist', 'Override dist directory', function() {
    var dir = this.args[0];
    if (dir) { grunt.config('dist', dir); }
  });

  grunt.registerTask('build', 'Create bootstrap build files', function() {
    var _ = grunt.util._;

    //If arguments define what modules to build, build those. Else, everything
    if (this.args.length) {
      this.args.forEach(findModule);
      grunt.config('filename', grunt.config('filenamecustom'));
    } else {
      grunt.file.expand({
        filter: 'isDirectory', cwd: '.'
      }, 'src/*').forEach(function(dir) {
        findModule(dir.split('/')[1]);
      });
    }

    var modules = grunt.config('modules');
    grunt.config('srcModules', _.pluck(modules, 'moduleName'));
    grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} ));
    grunt.config('demoModules', modules
      .filter(function(module) {
        return module.docs.md && module.docs.js && module.docs.html;
      })
      .sort(function(a, b) {
        if (a.name < b.name) { return -1; }
        if (a.name > b.name) { return 1; }
        return 0;
      })
    );

    var srcFiles = _.pluck(modules, 'srcFiles');
    var tpljsFiles = _.pluck(modules, 'tpljsFiles');
    //Set the concat task to concatenate the given src modules
    grunt.config('concat.dist.src', grunt.config('concat.dist.src')
                 .concat(srcFiles));
    //Set the concat-with-templates task to concat the given src & tpl modules
    grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
                 .concat(srcFiles).concat(tpljsFiles));

    grunt.task.run(['concat', 'uglify']);
  });

  grunt.registerTask('test', 'Run tests on singleRun karma server', function () {
    //this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI
    //we need to take settings for each one into account
    if (process.env.TRAVIS) {
      grunt.task.run('karma:travis');
    } else {
      var isToRunJenkinsTask = !!this.args.length;
      if(grunt.option('coverage')) {
        var karmaOptions = grunt.config.get('karma.options'),
          coverageOpts = grunt.config.get('karma.coverage');
        grunt.util._.extend(karmaOptions, coverageOpts);
        grunt.config.set('karma.options', karmaOptions);
      }
      grunt.task.run(this.args.length ? 'karma:jenkins' : 'karma:continuous');
    }
  });

  function setVersion(type, suffix) {
    var file = 'package.json';
    var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;
    var contents = grunt.file.read(file);
    var version;
    contents = contents.replace(VERSION_REGEX, function(match, left, center) {
      version = center;
      if (type) {
        version = require('semver').inc(version, type);
      }
      //semver.inc strips our suffix if it existed
      if (suffix) {
        version += '-' + suffix;
      }
      return left + version + '"';
    });
    grunt.log.ok('Version set to ' + version.cyan);
    grunt.file.write(file, contents);
    return version;
  }

  grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', function() {
    setVersion(this.args[0], this.args[1]);
  });

  grunt.registerMultiTask('shell', 'run shell commands', function() {
    var self = this;
    var sh = require('shelljs');
    self.data.forEach(function(cmd) {
      cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version);
      grunt.log.ok(cmd);
      var result = sh.exec(cmd,{silent:true});
      if (result.code !== 0) {
        grunt.fatal(result.output);
      }
    });
  });

  return grunt;
};
<|endoftext|>


<commit_before>// ==========================================================================
//                          DG.NumberToggleModel
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

/** @class DG.NumberToggleModel - The model for the parent cases that are available for toggling hide and show.

  @extends SC.Object
*/
DG.NumberToggleModel = SC.Object.extend( (function() {
/** @scope DG.NumberToggleModel.prototype */

  function reduceCase(prevCases, newCase) {
    prevCases.push(newCase);
    return prevCases;
  }

  function hideShowCases(iConfig, iCases, iShow) {
    var casesToHideShow = [];
    iCases.reduce(reduceCase, casesToHideShow);

    // recursively hide/show children
    iCases.forEach(function(iCase) {
      var tChildren = iCase.get('children');
      if (tChildren && tChildren.length)
        tChildren.reduce(reduceCase, casesToHideShow);
    });

    if (iShow)
      iConfig.showCases(casesToHideShow);
    else
      iConfig.hideCases(casesToHideShow);
  }

return {
  /**
    Assigned by the graph model that owns me.
    @property { DG.GraphDataConfiguration }
  */
  dataConfiguration: null,

  hiddenCasesBinding: '*dataConfiguration.hiddenCases',

  /**
   * Whether we are in show last parent case only mode
   * @property {Boolean}
   */
  lastMode: false,

  _cachedCaseCount: null,
  _cachedParentCases: null,

  invalidate: function() {
    this._cachedCaseCount = this._cachedParentCases = this._isHierarchical = null;

    // 'caseCount' is used as a proxy to indicate that some change occurred
    // GraphView.handleNumberToggleDidChange() observes 'caseCount'
    // not clear why invokeOnceLater() is (or was) required
    this.invokeOnceLater( this.propertyDidChange, 1, 'caseCount');
  },

  isEnabledDidChange: function() {
    // sync up with any changes that occurred while disabled
    if (this.get('isEnabled'))
      this.invalidate();
  }.observes('isEnabled'),

  /**
   * @property{SC.Array of DG.Case}
   */
  parentCases: function() {
    var tParents = [],
        tCases = this.getPath('dataConfiguration.allCases' ),
        isHierarchical = false;
    if( !tCases)
      return [];
    tCases = tCases.slice();

    function getUltimateParent(iCase) {
      var lastCase;
      while(iCase) {
        lastCase = iCase;
        iCase = iCase.get('parent');
        if (iCase) isHierarchical = true;
      }
      return lastCase;
    }

    // This check for whether we can use the cached parents isn't completely foolproof because
    // cases could come and go leaving us with the same number between calls.
    if( tCases.get('length') === this._cachedCaseCount) {
      tParents = this._cachedParentCases;
    }
    else {
      tCases.forEach( function( iCase) {
        var tParent = getUltimateParent(iCase);
        if( tParent && tParents.indexOf( tParent ) < 0 ) {
          tParents.push( tParent );
        }
      });
      this._cachedCaseCount = tCases.get('length');
      this._cachedParentCases = tParents;
      this._isHierarchical = isHierarchical;
    }
    return tParents;
  }.property(),
  parentCasesDidChange: function() {
    this.notifyPropertyChange('parentCases');
  }.observes('*dataConfiguration.cases'),

  getFirstParentAttribute: function() {
    var dataContext = this.getPath('dataConfiguration.dataContext'),
        collectionClient = dataContext.getCollectionAtIndex(0),
        attrs = collectionClient && collectionClient.getPath('collection.attrs');
    return attrs && attrs[0];
  },

  getParentLabel: function(iIndex) {
    var cases = this.get('parentCases'),
        tCase = cases && cases[iIndex],
        attr = this.getFirstParentAttribute(),
        caseAttrValue = tCase && attr && tCase.getStrValue(attr.get('id'));
    return !SC.empty(caseAttrValue) ? caseAttrValue : (iIndex + 1).toString();
  },

  getParentTooltip: function(iIndex) {
    return SC.String.loc('DG.NumberToggleView.indexTooltip');
  },

  /**
   * @property {Number}
   */
  numberOfParents: function() {
    return this.get('parentCases' ).length;
  }.property('parentCases'),

  /**
   * True if cases have parents
   * @property{Integer}
   */
  indicesRepresentChildren: function() {
    this.get('parentCases');
    return this._isHierarchical;
  }.property('numberOfParents'),

  /**
   * Return the number of indices that should be displayed
   * @property{Integer}
   */
  numberOfToggleIndices: function() {
    var dataConfiguration = this.get('dataConfiguration'),
      allCases = (dataConfiguration && dataConfiguration.get('allCases')),
      allCasesLength = allCases? allCases.length(): 0;
    if( this.get('indicesRepresentChildren'))
      return this.get('numberOfParents');
    else // There are no parents, so each case gets an index
      return allCasesLength;
  }.property(),
  numberOfToggleIndicesDidChange: function() {
    this.notifyPropertyChange('numberOfToggleIndices');
  }.observes('*dataConfiguration.allCases'),

  /**
   * Return the children of the parent with the given index.
   *
   * @param iIndex {Number}
   * @return {Array} of DG.Case
   */
  childrenOfParent: function( iIndex) {
    var tParents = this.get('parentCases' ),
        tParent = (iIndex < tParents.length) ? tParents[ iIndex] : null,
        tChildren = tParent ? tParent.get('children').slice() : [];
    // use for-loop since tChildren is modified recursively
    for (var i = 0; i < tChildren.get('length'); ++i) {
      var child = tChildren.objectAt(i),
          descendants = child.get('children');
      if (descendants && descendants.get('length')) {
        descendants.forEach(function(iCase) {
          tChildren.push(iCase);
        });
      }
    }
    return tChildren;
  },

  /**
   * @return{Boolean}
   */
  allCasesAreVisible: function() {
    var hiddenCases = this.get('hiddenCases');
    return !hiddenCases || !hiddenCases.length;
  },

  /*
   * For tracking whether we are in the process of making visibility changes.
   * Allows differential response to visibility changes caused by others.
   */
  _isChangingVisibility: 0,

  beginVisibilityChanges: function() {
    ++ this._isChangingVisibility;
  },

  endVisibilityChanges: function() {
    // use invokeLater() so notifications have a chance to propagate
    this.invokeLater(function() {
      -- this._isChangingVisibility;
    }.bind(this));
  },

  isChangingVisibility: function() {
    return this._isChangingVisibility > 0;
  },

  /**
   * If any cases are hidden, show them. Otherwise hide all cases
   *
   */
  changeAllCaseVisibility: function() {
    var tConfig = this.get('dataConfiguration' );
    this.beginVisibilityChanges();
    if( this.allCasesAreVisible()) {
      hideShowCases(tConfig, tConfig.get('cases'), false);
    }
    else {
      hideShowCases(tConfig, tConfig.get('hiddenCases'), true);
    }
    this.endVisibilityChanges();
  },

  getCasesForIndex: function(iIndex) {
    var tConfig = this.get('dataConfiguration'),
        tResultCases;

    if (this.get('indicesRepresentChildren')) {
      tResultCases = this.childrenOfParent(iIndex);
    }
    else {
      var tAllCases = tConfig ? tConfig.get('allCases') : [],
          tCases = tAllCases ? tAllCases.slice() : [],
          tCase = (tCases.get('length') > iIndex) ? tCases.at(iIndex) : null;
      tResultCases = tCase ? [tCase] : null;
    }
    return tResultCases;
  },

  /**
   * If any cases that are children of the parent with the given index are visible, hide them.
   * Otherwise, unhide all cases that are children of the parent with the given index.
   *
   * @param iIndex {Number}
   */
  toggleChildrenVisibility: function( iIndex) {
    var tChildren = this.childrenOfParent( iIndex ),
        tConfig = this.get('dataConfiguration');

    this.beginVisibilityChanges();
    hideShowCases(tConfig, tChildren, this.allChildrenAreHidden(iIndex));
    this.endVisibilityChanges();
  },

  /**
   * Whether we toggle visibility of children or a single parent depends what the indices represent
   * @param iIndex
   */
  toggleVisibility: function( iIndex) {
    if( this.get('indicesRepresentChildren')) {
      this.toggleChildrenVisibility( iIndex);
    }
    else {
      var tConfig = this.get('dataConfiguration'),
          tCases = tConfig ? tConfig.get('allCases').slice() : [],
          tHidden = tConfig ? tConfig.get('hiddenCases' ) : [],
          tCase = (tCases.get('length') > iIndex) ? tCases.at(iIndex) : null;
      this.beginVisibilityChanges();
      hideShowCases(tConfig, [tCase], tHidden.indexOf(tCase) >= 0);
      this.endVisibilityChanges();
    }
  },

  /**
   * Hide/show the children of the specified parent case
   * @param {Number}  iIndex - parent case index
   * @param {Boolean} iVisible - true to show; false to hide
   */
  setVisibility: function(iIndex, iVisible) {
    var tConfig = this.get('dataConfiguration'),
        tCasesToHideShow = this.getCasesForIndex(iIndex);

    if (tCasesToHideShow) {
      this.beginVisibilityChanges();
      hideShowCases(tConfig, tCasesToHideShow, iVisible);
      this.endVisibilityChanges();
    }
  },

  /**
   * Observer function for 'lastMode' changes
   * Enabling 'lastMode' hides all but the last parent case.
   * Disabling 'lastMode' exits the mode but doesn't change case visibility.
   */
  lastModeDidChange: function() {
    var lastMode = this.get('lastMode');
    if (lastMode) {
      this.showOnlyLastParentCase();
    }
  }.observes('lastMode'),

  /**
   * Show the last parent case and hide all the other parent cases.
   */
  showOnlyLastParentCase: function() {
    var toggleIndex, toggleCount = this.get('numberOfToggleIndices'),
        casesToHide = [], toggleCases;

    this.beginVisibilityChanges();

    // accumulate cases to hide so they can be hidden all at once
    for (toggleIndex = 0; toggleIndex < toggleCount - 1; ++toggleIndex) {
      toggleCases = this.getCasesForIndex(toggleIndex);
      // append toggleCases to casesToHide without duplicating any arrays
      toggleCases.reduce(reduceCase, casesToHide);
    }
    // hide all cases in one fell swoop
    if (casesToHide && casesToHide.length)
      hideShowCases(this.get('dataConfiguration'), casesToHide, false);
    // show the last parent case
    this.setVisibility(toggleIndex, true);

    this.endVisibilityChanges();
  },

  /**
   *
   * @param iIndex
   * @return {Boolean}
   */
  allChildrenAreHidden: function( iIndex) {
    var tChildren = this.childrenOfParent( iIndex ),
        tPlotted = this.getPath('dataConfiguration.cases'),
        tHidden = this.get('hiddenCases'),
        tPlottedMap = {},
        tHiddenMap = {};

    // Note: Ideally, these maps should be cached, as they're currently being created for
    // each parent case independently, but figuring out the right time to invalidate the
    // caches is non-trivial, and so is left as a potential future optimization.

    // id map of all plotted cases (does not include cases not plotted due to missing values)
    (tPlotted || []).forEach(function(iCase) { tPlottedMap[iCase.get('id')] = true; });
    // id map of all hidden cases
    (tHidden || []).forEach(function(iCase) {
      if(iCase)
        tHiddenMap[iCase.get('id')] = true;
    });

    return !tChildren.some(function(iCase) {
                            var caseID = iCase.get('id');
                            // to be visible cases must be plotted and not hidden
                            return tPlottedMap[caseID] && !tHiddenMap[caseID];
                          });
  },

  /**
   * Depends on what indices represent
   * @param iIndex
   * @return {Boolean}
   */
  casesForIndexAreHidden: function( iIndex) {
    if( this.get('indicesRepresentChildren')) {
      return this.allChildrenAreHidden( iIndex);
    }
    else {
      var tConfig = this.get('dataConfiguration'),
          tCases = tConfig ? tConfig.get('allCases').slice() : [],
          tHidden = tConfig ? tConfig.get('hiddenCases' ) : [],
          tCase = (tCases.get('length') > iIndex) ? tCases.at(iIndex) : null;
      return( tHidden.indexOf( tCase) >= 0);
    }
  },

  /**
   * Note: returns true if no cases are hidden when there are no cases
   * @return {Boolean}
   */
  allCasesAreHidden: function() {
    return this.get('dataConfiguration' ).getCaseCount() === 0;
  },

  /**
   * Respond to shown/hidden cases
   */
  hiddenCasesDidChange: function() {
    // if user hides/shows cases via other means, exit lastMode
    if (this.get('lastMode') && !this.isChangingVisibility())
      this.set('lastMode', false);
  }.observes('hiddenCases'),

  isAffectedByChange: function(iChange) {

    function isRelevantChange(iChange) {
      var operations = ['createCollection', 'deleteCollection', 'moveCases', 'moveAttribute',
                          'createCases', 'createCase', 'deleteCases', 'createAttributes',
                          'updateAttributes', 'deleteAttributes'];
      return operations.indexOf(iChange.operation) >= 0;
    }

    var isToggleAttributeChange = function(iChange) {
      if (iChange.operation === 'updateCases') {
        var toggleAttr = this.getFirstParentAttribute(),
            toggleAttrID = toggleAttr && toggleAttr.get('id');
        if (iChange.attributeIDs) {
          if (iChange.attributeIDs.indexOf(toggleAttrID) >= 0)
            return true;
        }
      }
      return false;
    }.bind(this);

    return isRelevantChange(iChange) || isToggleAttributeChange(iChange);
  },

  /**
   * When the data context changes we notify
   */
  handleDataContextNotification: function(iNotifier, iChange) {
    if (this.get('isEnabled')) {
      // if cases are created while in lastMode, ensure only last parent is visible
      if (this.get('lastMode') && (iChange.operation.indexOf('createCase') === 0)) {
        this.invokeOnce(function() { this.showOnlyLastParentCase(); }.bind(this));
        return;
      }
      if( this.isAffectedByChange(iChange))
        this.invalidate();
    }
  }

};

})());
<commit_msg>[#155522696] Bug fix: Making a graph causes error if graph has parent toggles visible.<commit_after>// ==========================================================================
//                          DG.NumberToggleModel
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

/** @class DG.NumberToggleModel - The model for the parent cases that are available for toggling hide and show.

  @extends SC.Object
*/
DG.NumberToggleModel = SC.Object.extend( (function() {
/** @scope DG.NumberToggleModel.prototype */

  function reduceCase(prevCases, newCase) {
    prevCases.push(newCase);
    return prevCases;
  }

  function hideShowCases(iConfig, iCases, iShow) {
    var casesToHideShow = [];
    iCases.reduce(reduceCase, casesToHideShow);

    // recursively hide/show children
    iCases.forEach(function(iCase) {
      var tChildren = iCase.get('children');
      if (tChildren && tChildren.length)
        tChildren.reduce(reduceCase, casesToHideShow);
    });

    if (iShow)
      iConfig.showCases(casesToHideShow);
    else
      iConfig.hideCases(casesToHideShow);
  }

return {
  /**
    Assigned by the graph model that owns me.
    @property { DG.GraphDataConfiguration }
  */
  dataConfiguration: null,

  hiddenCasesBinding: '*dataConfiguration.hiddenCases',

  /**
   * Whether we are in show last parent case only mode
   * @property {Boolean}
   */
  lastMode: false,

  _cachedCaseCount: null,
  _cachedParentCases: null,

  invalidate: function() {
    this._cachedCaseCount = this._cachedParentCases = this._isHierarchical = null;

    // 'caseCount' is used as a proxy to indicate that some change occurred
    // GraphView.handleNumberToggleDidChange() observes 'caseCount'
    // not clear why invokeOnceLater() is (or was) required
    this.invokeOnceLater( this.propertyDidChange, 1, 'caseCount');
  },

  isEnabledDidChange: function() {
    // sync up with any changes that occurred while disabled
    if (this.get('isEnabled'))
      this.invalidate();
  }.observes('isEnabled'),

  /**
   * @property{SC.Array of DG.Case}
   */
  parentCases: function() {
    var tParents = [],
        tCases = this.getPath('dataConfiguration.allCases' ),
        isHierarchical = false;
    if( !tCases)
      return [];
    tCases = tCases.slice();

    function getUltimateParent(iCase) {
      var lastCase;
      while(iCase) {
        lastCase = iCase;
        iCase = iCase.get('parent');
        if (iCase) isHierarchical = true;
      }
      return lastCase;
    }

    // This check for whether we can use the cached parents isn't completely foolproof because
    // cases could come and go leaving us with the same number between calls.
    if( tCases.get('length') === this._cachedCaseCount) {
      tParents = this._cachedParentCases;
    }
    else {
      tCases.forEach( function( iCase) {
        var tParent = getUltimateParent(iCase);
        if( tParent && tParents.indexOf( tParent ) < 0 ) {
          tParents.push( tParent );
        }
      });
      this._cachedCaseCount = tCases.get('length');
      this._cachedParentCases = tParents;
      this._isHierarchical = isHierarchical;
    }
    return tParents;
  }.property(),
  parentCasesDidChange: function() {
    this.notifyPropertyChange('parentCases');
  }.observes('*dataConfiguration.cases'),

  getFirstParentAttribute: function() {
    var dataContext = this.getPath('dataConfiguration.dataContext'),
        collectionClient = dataContext.getCollectionAtIndex(0),
        attrs = collectionClient && collectionClient.getPath('collection.attrs');
    return attrs && attrs[0];
  },

  getParentLabel: function(iIndex) {
    var cases = this.get('parentCases'),
        tCase = cases && cases[iIndex],
        attr = this.getFirstParentAttribute(),
        caseAttrValue = tCase && attr && tCase.getStrValue(attr.get('id'));
    return !SC.empty(caseAttrValue) ? caseAttrValue : (iIndex + 1).toString();
  },

  getParentTooltip: function(iIndex) {
    return SC.String.loc('DG.NumberToggleView.indexTooltip');
  },

  /**
   * @property {Number}
   */
  numberOfParents: function() {
    return this.get('parentCases' ).length;
  }.property('parentCases'),

  /**
   * True if cases have parents
   * @property{Integer}
   */
  indicesRepresentChildren: function() {
    this.get('parentCases');
    return this._isHierarchical;
  }.property('numberOfParents'),

  /**
   * Return the number of indices that should be displayed
   * @property{Integer}
   */
  numberOfToggleIndices: function() {
    var dataConfiguration = this.get('dataConfiguration'),
      allCases = (dataConfiguration && dataConfiguration.get('allCases')),
      allCasesLength = allCases? allCases.length(): 0;
    if( this.get('indicesRepresentChildren'))
      return this.get('numberOfParents');
    else // There are no parents, so each case gets an index
      return allCasesLength;
  }.property(),
  numberOfToggleIndicesDidChange: function() {
    this.notifyPropertyChange('numberOfToggleIndices');
  }.observes('*dataConfiguration.allCases'),

  /**
   * Return the children of the parent with the given index.
   *
   * @param iIndex {Number}
   * @return {Array} of DG.Case
   */
  childrenOfParent: function( iIndex) {
    var tParents = this.get('parentCases' ),
        tParent = (iIndex < tParents.length) ? tParents[ iIndex] : null,
        tChildren = tParent ? tParent.get('children').slice() : [];
    // use for-loop since tChildren is modified recursively
    for (var i = 0; i < tChildren.get('length'); ++i) {
      var child = tChildren.objectAt(i),
          descendants = child.get('children');
      if (descendants && descendants.get('length')) {
        descendants.forEach(function(iCase) {
          tChildren.push(iCase);
        });
      }
    }
    return tChildren;
  },

  /**
   * @return{Boolean}
   */
  allCasesAreVisible: function() {
    var hiddenCases = this.get('hiddenCases');
    return !hiddenCases || !hiddenCases.length;
  },

  /*
   * For tracking whether we are in the process of making visibility changes.
   * Allows differential response to visibility changes caused by others.
   */
  _isChangingVisibility: 0,

  beginVisibilityChanges: function() {
    ++ this._isChangingVisibility;
  },

  endVisibilityChanges: function() {
    // use invokeLater() so notifications have a chance to propagate
    this.invokeLater(function() {
      -- this._isChangingVisibility;
    }.bind(this));
  },

  isChangingVisibility: function() {
    return this._isChangingVisibility > 0;
  },

  /**
   * If any cases are hidden, show them. Otherwise hide all cases
   *
   */
  changeAllCaseVisibility: function() {
    var tConfig = this.get('dataConfiguration' );
    this.beginVisibilityChanges();
    if( this.allCasesAreVisible()) {
      hideShowCases(tConfig, tConfig.get('cases'), false);
    }
    else {
      hideShowCases(tConfig, tConfig.get('hiddenCases'), true);
    }
    this.endVisibilityChanges();
  },

  getCasesForIndex: function(iIndex) {
    var tConfig = this.get('dataConfiguration'),
        tResultCases;

    if (this.get('indicesRepresentChildren')) {
      tResultCases = this.childrenOfParent(iIndex);
    }
    else {
      var tAllCases = tConfig ? tConfig.get('allCases') : [],
          tCasesArray = tAllCases ? tAllCases.slice() : [],
          tCase = (tCasesArray.length > iIndex) ? tCasesArray[iIndex] : null;
      tResultCases = tCase ? [tCase] : null;
    }
    return tResultCases;
  },

  /**
   * If any cases that are children of the parent with the given index are visible, hide them.
   * Otherwise, unhide all cases that are children of the parent with the given index.
   *
   * @param iIndex {Number}
   */
  toggleChildrenVisibility: function( iIndex) {
    var tChildren = this.childrenOfParent( iIndex ),
        tConfig = this.get('dataConfiguration');

    this.beginVisibilityChanges();
    hideShowCases(tConfig, tChildren, this.allChildrenAreHidden(iIndex));
    this.endVisibilityChanges();
  },

  /**
   * Whether we toggle visibility of children or a single parent depends what the indices represent
   * @param iIndex
   */
  toggleVisibility: function( iIndex) {
    if( this.get('indicesRepresentChildren')) {
      this.toggleChildrenVisibility( iIndex);
    }
    else {
      var tConfig = this.get('dataConfiguration'),
          tAllCasesArray = tConfig ? tConfig.get('allCases').slice() : [],
          tHidden = tConfig ? tConfig.get('hiddenCases' ) : [],
          tCase = (tAllCasesArray.length > iIndex) ? tAllCasesArray[iIndex] : null;
      this.beginVisibilityChanges();
      hideShowCases(tConfig, [tCase], tHidden.indexOf(tCase) >= 0);
      this.endVisibilityChanges();
    }
  },

  /**
   * Hide/show the children of the specified parent case
   * @param {Number}  iIndex - parent case index
   * @param {Boolean} iVisible - true to show; false to hide
   */
  setVisibility: function(iIndex, iVisible) {
    var tConfig = this.get('dataConfiguration'),
        tCasesToHideShow = this.getCasesForIndex(iIndex);

    if (tCasesToHideShow) {
      this.beginVisibilityChanges();
      hideShowCases(tConfig, tCasesToHideShow, iVisible);
      this.endVisibilityChanges();
    }
  },

  /**
   * Observer function for 'lastMode' changes
   * Enabling 'lastMode' hides all but the last parent case.
   * Disabling 'lastMode' exits the mode but doesn't change case visibility.
   */
  lastModeDidChange: function() {
    var lastMode = this.get('lastMode');
    if (lastMode) {
      this.showOnlyLastParentCase();
    }
  }.observes('lastMode'),

  /**
   * Show the last parent case and hide all the other parent cases.
   */
  showOnlyLastParentCase: function() {
    var toggleIndex, toggleCount = this.get('numberOfToggleIndices'),
        casesToHide = [], toggleCases;

    this.beginVisibilityChanges();

    // accumulate cases to hide so they can be hidden all at once
    for (toggleIndex = 0; toggleIndex < toggleCount - 1; ++toggleIndex) {
      toggleCases = this.getCasesForIndex(toggleIndex);
      // append toggleCases to casesToHide without duplicating any arrays
      toggleCases.reduce(reduceCase, casesToHide);
    }
    // hide all cases in one fell swoop
    if (casesToHide && casesToHide.length)
      hideShowCases(this.get('dataConfiguration'), casesToHide, false);
    // show the last parent case
    this.setVisibility(toggleIndex, true);

    this.endVisibilityChanges();
  },

  /**
   *
   * @param iIndex
   * @return {Boolean}
   */
  allChildrenAreHidden: function( iIndex) {
    var tChildren = this.childrenOfParent( iIndex ),
        tPlotted = this.getPath('dataConfiguration.cases'),
        tHidden = this.get('hiddenCases'),
        tPlottedMap = {},
        tHiddenMap = {};

    // Note: Ideally, these maps should be cached, as they're currently being created for
    // each parent case independently, but figuring out the right time to invalidate the
    // caches is non-trivial, and so is left as a potential future optimization.

    // id map of all plotted cases (does not include cases not plotted due to missing values)
    (tPlotted || []).forEach(function(iCase) { tPlottedMap[iCase.get('id')] = true; });
    // id map of all hidden cases
    (tHidden || []).forEach(function(iCase) {
      if(iCase)
        tHiddenMap[iCase.get('id')] = true;
    });

    return !tChildren.some(function(iCase) {
                            var caseID = iCase.get('id');
                            // to be visible cases must be plotted and not hidden
                            return tPlottedMap[caseID] && !tHiddenMap[caseID];
                          });
  },

  /**
   * Depends on what indices represent
   * @param iIndex
   * @return {Boolean}
   */
  casesForIndexAreHidden: function( iIndex) {
    if( this.get('indicesRepresentChildren')) {
      return this.allChildrenAreHidden( iIndex);
    }
    else {
      var tConfig = this.get('dataConfiguration'),
          tAllCasesArray = tConfig ? tConfig.get('allCases').slice() : [],
          tHidden = tConfig ? tConfig.get('hiddenCases' ) : [],
          tCase = (tAllCasesArray.length > iIndex) ? tAllCasesArray[iIndex] : null;
      return( tHidden.indexOf( tCase) >= 0);
    }
  },

  /**
   * Note: returns true if no cases are hidden when there are no cases
   * @return {Boolean}
   */
  allCasesAreHidden: function() {
    return this.get('dataConfiguration' ).getCaseCount() === 0;
  },

  /**
   * Respond to shown/hidden cases
   */
  hiddenCasesDidChange: function() {
    // if user hides/shows cases via other means, exit lastMode
    if (this.get('lastMode') && !this.isChangingVisibility())
      this.set('lastMode', false);
  }.observes('hiddenCases'),

  isAffectedByChange: function(iChange) {

    function isRelevantChange(iChange) {
      var operations = ['createCollection', 'deleteCollection', 'moveCases', 'moveAttribute',
                          'createCases', 'createCase', 'deleteCases', 'createAttributes',
                          'updateAttributes', 'deleteAttributes'];
      return operations.indexOf(iChange.operation) >= 0;
    }

    var isToggleAttributeChange = function(iChange) {
      if (iChange.operation === 'updateCases') {
        var toggleAttr = this.getFirstParentAttribute(),
            toggleAttrID = toggleAttr && toggleAttr.get('id');
        if (iChange.attributeIDs) {
          if (iChange.attributeIDs.indexOf(toggleAttrID) >= 0)
            return true;
        }
      }
      return false;
    }.bind(this);

    return isRelevantChange(iChange) || isToggleAttributeChange(iChange);
  },

  /**
   * When the data context changes we notify
   */
  handleDataContextNotification: function(iNotifier, iChange) {
    if (this.get('isEnabled')) {
      // if cases are created while in lastMode, ensure only last parent is visible
      if (this.get('lastMode') && (iChange.operation.indexOf('createCase') === 0)) {
        this.invokeOnce(function() { this.showOnlyLastParentCase(); }.bind(this));
        return;
      }
      if( this.isAffectedByChange(iChange))
        this.invalidate();
    }
  }

};

})());
<|endoftext|>