<commit_before>/**
* This source code is quoted from rc-tabs.
* homepage: https://github.com/react-component/tabs
*/
import React from "react";
import KeyCode from "./KeyCode";
import TabPane from "./TabPane";
import classnames from "classnames";
import TabContent from "./TabContent";
import ScrollableInkTabBar from "./ScrollableInkTabBar";
import PropTypes from 'prop-types';
import createClass from 'create-react-class';
import Icon from 'bee-icon';

function noop() {}

function getDefaultActiveKey(props) {
  let activeKey;
  React.Children.forEach(props.children, child => {
    if (child && !activeKey && !child.props.disabled) {
      activeKey = child.key;
    }
  });
  return activeKey;
}

const Tabs = createClass({
  propTypes: {
    destroyInactiveTabPane: PropTypes.bool,
    renderTabBar: PropTypes.func.isRequired,
    renderTabContent: PropTypes.func.isRequired,
    onChange: PropTypes.func,
    children: PropTypes.any,
    clsPrefix: PropTypes.string,
    className: PropTypes.string,
    tabBarPosition: PropTypes.string,
    style: PropTypes.object,
    tabBarStyle: PropTypes.oneOf([
      "simple",
      "fill",
      "primary",
      "upborder",
      "fade",
      "downborder",
      "trapezoid",
      "editable-card"
    ])
  },

  getDefaultProps() {
    return {
      clsPrefix: "u-tabs",
      destroyInactiveTabPane: false,
      onChange: noop,
      tabBarPosition: "top",
      style: {},
      renderTabContent: () => <TabContent />,
      renderTabBar: () => <ScrollableInkTabBar />,
      tabBarStyle: "simple",
        animated: true
    };
  },

  getInitialState() {
    const props = this.props;
    let activeKey;
    if ("activeKey" in props) {
      activeKey = props.activeKey;
    } else if ("defaultActiveKey" in props) {
      activeKey = props.defaultActiveKey;
    } else {
      activeKey = getDefaultActiveKey(props);
    }
    return {
      activeKey
    };
  },

  componentWillReceiveProps(nextProps) {
    if ("activeKey" in nextProps) {
      this.setState({
        activeKey: nextProps.activeKey
      });
    }
  },

  onTabClick(activeKey) {
    this.props.onTabClick && this.props.onTabClick(activeKey);
    if (this.tabBar.props.onTabClick) {
      this.tabBar.props.onTabClick(activeKey);
    }
    this.setActiveKey(activeKey);
  },

  onNavKeyDown(e) {
    const eventKeyCode = e.keyCode;
    if (eventKeyCode === KeyCode.RIGHT || eventKeyCode === KeyCode.DOWN) {
      e.preventDefault();
      const nextKey = this.getNextActiveKey(true);
      this.onTabClick(nextKey);
    } else if (eventKeyCode === KeyCode.LEFT || eventKeyCode === KeyCode.UP) {
      e.preventDefault();
      const previousKey = this.getNextActiveKey(false);
      this.onTabClick(previousKey);
    }
  },

  setActiveKey(activeKey) {
    if (this.state.activeKey !== activeKey) {
      if (!("activeKey" in this.props)) {
        this.setState({
          activeKey
        });
      }
      this.props.onChange(activeKey);
    }
  },

  getNextActiveKey(next) {
    const activeKey = this.state.activeKey;
    const children = [];
    React.Children.forEach(this.props.children, c => {
      if (c && !c.props.disabled) {
        if (next) {
          children.push(c);
        } else {
          children.unshift(c);
        }
      }
    });
    const length = children.length;
    let ret = length && children[0].key;
    children.forEach((child, i) => {
      if (child.key === activeKey) {
        if (i === length - 1) {
          ret = children[0].key;
        } else {
          ret = children[i + 1].key;
        }
      }
    });
    return ret;
  },
  onPrevClick(e){
    this.props.onPrevClick && this.props.onPrevClick(e)
  },
  onNextClick(e){
    this.props.onNextClick && this.props.onNextClick(e)
  },
  createNewTab(targetKey){
    const { onEdit } = this.props;
    if (onEdit) {
      onEdit(targetKey, 'add');
    }
  },
  removeTab(targetKey, e){
    e.stopPropagation();
    if (!targetKey) {
      return;
    }

    const { onEdit } = this.props;
    if (onEdit) {
      onEdit(targetKey, 'remove');
    }
  },

  render() {
    const props = this.props;
    const {
      activeKey,
      defaultActiveKey,
      clsPrefix,
      tabBarPosition,
      className,
      renderTabContent,
      renderTabBar,
      tabBarStyle,
      extraContent,
      animated,
      tabIndex,
      children,
      hideAdd,
      scrollAnimated,
      inkBarAnimated,
      useTransform3d,
      destroyInactiveTabPane,
      onTabClick,
      onEdit,
      onNextClick,
      onPrevClick,
      ...others
    } = props;

    const cls = classnames({
      [clsPrefix]: true,
      [`${clsPrefix}-${tabBarPosition}`]: true,
      [className]: !!className,
      [`${clsPrefix}-${tabBarStyle}`]: true
    });

    this.tabBar = renderTabBar();

    // only card type tabs can be added and closed
    let childrenWithClose = [],
        tabBarExtraContent = extraContent;
    if (tabBarStyle === 'editable-card') {
      childrenWithClose = [];
      React.Children.forEach(children, (child, index) => {
        if (!React.isValidElement(child)) return child;
        let { closable } = child.props;
        closable = typeof closable === 'undefined' ? true : closable;
        const closeIcon = closable ? (
          <Icon
            type="uf-close"
            className={`${clsPrefix}-close-x`}
            onClick={e => this.removeTab(child.key, e)}
          />
        ) : null;
        childrenWithClose.push(
          React.cloneElement(child, {
            tab: (
              <div className={closable ? undefined : `${clsPrefix}-tab-unclosable`}>
                {child.props.tab}
                {closeIcon}

            ),
            key: child.key || index,
          }),
        );
      });
      // Add new tab handler
      if (!hideAdd) {
        tabBarExtraContent = (

            <Icon type="uf-add-s-o" className={`${clsPrefix}-new-tab`} onClick={this.createNewTab} />
            {extraContent}

        );
      }
    }

    const contents = [
      React.cloneElement(this.tabBar, {
        clsPrefix,
        key: "tabBar",
        onKeyDown: this.onNavKeyDown,
        tabBarPosition,
        extraContent: tabBarExtraContent,
        onTabClick: this.onTabClick,
        panels: childrenWithClose.length > 0 ? childrenWithClose : children,
        activeKey: this.state.activeKey,
        tabIndex,
        onPrevClick: this.onPrevClick,
        onNextClick: this.onNextClick,
      }),
      React.cloneElement(renderTabContent(), {
        clsPrefix,
        tabBarPosition,
          animated,
        activeKey: this.state.activeKey,
        destroyInactiveTabPane: props.destroyInactiveTabPane,
        children: props.children,
          // style: { height: '100%' },
        onChange: this.setActiveKey,
        key: "tabContent"
      })
    ];
    if (tabBarPosition === "bottom") {
      contents.reverse();
    }
    return (

        {contents}

    );
  }
});

Tabs.TabPane = TabPane;

export  {Tabs};
<commit_msg>fix: 修复Tabs中checkbox点击自动触发Tabs的onChange<commit_after>/**
* This source code is quoted from rc-tabs.
* homepage: https://github.com/react-component/tabs
*/
import React from "react";
import KeyCode from "./KeyCode";
import TabPane from "./TabPane";
import classnames from "classnames";
import TabContent from "./TabContent";
import ScrollableInkTabBar from "./ScrollableInkTabBar";
import PropTypes from 'prop-types';
import createClass from 'create-react-class';
import Icon from 'bee-icon';

function noop() {}

function getDefaultActiveKey(props) {
  let activeKey;
  React.Children.forEach(props.children, child => {
    if (child && !activeKey && !child.props.disabled) {
      activeKey = child.key;
    }
  });
  return activeKey;
}

const Tabs = createClass({
  propTypes: {
    destroyInactiveTabPane: PropTypes.bool,
    renderTabBar: PropTypes.func.isRequired,
    renderTabContent: PropTypes.func.isRequired,
    onChange: PropTypes.func,
    children: PropTypes.any,
    clsPrefix: PropTypes.string,
    className: PropTypes.string,
    tabBarPosition: PropTypes.string,
    style: PropTypes.object,
    tabBarStyle: PropTypes.oneOf([
      "simple",
      "fill",
      "primary",
      "upborder",
      "fade",
      "downborder",
      "trapezoid",
      "editable-card"
    ])
  },

  getDefaultProps() {
    return {
      clsPrefix: "u-tabs",
      destroyInactiveTabPane: false,
      onChange: noop,
      tabBarPosition: "top",
      style: {},
      renderTabContent: () => <TabContent />,
      renderTabBar: () => <ScrollableInkTabBar />,
      tabBarStyle: "simple",
        animated: true
    };
  },

  getInitialState() {
    const props = this.props;
    let activeKey;
    if ("activeKey" in props) {
      activeKey = props.activeKey;
    } else if ("defaultActiveKey" in props) {
      activeKey = props.defaultActiveKey;
    } else {
      activeKey = getDefaultActiveKey(props);
    }
    return {
      activeKey
    };
  },

  componentWillReceiveProps(nextProps) {
    if ("activeKey" in nextProps) {
      this.setState({
        activeKey: nextProps.activeKey
      });
    }
  },

  onTabClick(activeKey) {
    this.props.onTabClick && this.props.onTabClick(activeKey);
    if (this.tabBar.props.onTabClick) {
      this.tabBar.props.onTabClick(activeKey);
    }
    this.setActiveKey(activeKey);
  },

  onNavKeyDown(e) {
    const eventKeyCode = e.keyCode;
    if (eventKeyCode === KeyCode.RIGHT || eventKeyCode === KeyCode.DOWN) {
      e.preventDefault();
      const nextKey = this.getNextActiveKey(true);
      this.onTabClick(nextKey);
    } else if (eventKeyCode === KeyCode.LEFT || eventKeyCode === KeyCode.UP) {
      e.preventDefault();
      const previousKey = this.getNextActiveKey(false);
      this.onTabClick(previousKey);
    }
  },

  setActiveKey(activeKey) {
    if (this.state.activeKey !== activeKey) {
      if (!("activeKey" in this.props)) {
        this.setState({
          activeKey
        });
      }
      this.props.onChange(activeKey);
    }
  },

  getNextActiveKey(next) {
    const activeKey = this.state.activeKey;
    const children = [];
    React.Children.forEach(this.props.children, c => {
      if (c && !c.props.disabled) {
        if (next) {
          children.push(c);
        } else {
          children.unshift(c);
        }
      }
    });
    const length = children.length;
    let ret = length && children[0].key;
    children.forEach((child, i) => {
      if (child.key === activeKey) {
        if (i === length - 1) {
          ret = children[0].key;
        } else {
          ret = children[i + 1].key;
        }
      }
    });
    return ret;
  },
  onPrevClick(e){
    this.props.onPrevClick && this.props.onPrevClick(e)
  },
  onNextClick(e){
    this.props.onNextClick && this.props.onNextClick(e)
  },
  createNewTab(targetKey){
    const { onEdit } = this.props;
    if (onEdit) {
      onEdit(targetKey, 'add');
    }
  },
  removeTab(targetKey, e){
    e.stopPropagation();
    if (!targetKey) {
      return;
    }

    const { onEdit } = this.props;
    if (onEdit) {
      onEdit(targetKey, 'remove');
    }
  },

  render() {
    const props = this.props;
    const {
      activeKey,
      defaultActiveKey,
      clsPrefix,
      tabBarPosition,
      className,
      renderTabContent,
      renderTabBar,
      tabBarStyle,
      extraContent,
      animated,
      tabIndex,
      children,
      hideAdd,
      scrollAnimated,
      inkBarAnimated,
      useTransform3d,
      destroyInactiveTabPane,
      onTabClick,
      onEdit,
      onNextClick,
      onPrevClick,
      onChange,
      ...others
    } = props;

    const cls = classnames({
      [clsPrefix]: true,
      [`${clsPrefix}-${tabBarPosition}`]: true,
      [className]: !!className,
      [`${clsPrefix}-${tabBarStyle}`]: true
    });

    this.tabBar = renderTabBar();

    // only card type tabs can be added and closed
    let childrenWithClose = [],
        tabBarExtraContent = extraContent;
    if (tabBarStyle === 'editable-card') {
      childrenWithClose = [];
      React.Children.forEach(children, (child, index) => {
        if (!React.isValidElement(child)) return child;
        let { closable } = child.props;
        closable = typeof closable === 'undefined' ? true : closable;
        const closeIcon = closable ? (
          <Icon
            type="uf-close"
            className={`${clsPrefix}-close-x`}
            onClick={e => this.removeTab(child.key, e)}
          />
        ) : null;
        childrenWithClose.push(
          React.cloneElement(child, {
            tab: (
              <div className={closable ? undefined : `${clsPrefix}-tab-unclosable`}>
                {child.props.tab}
                {closeIcon}

            ),
            key: child.key || index,
          }),
        );
      });
      // Add new tab handler
      if (!hideAdd) {
        tabBarExtraContent = (

            <Icon type="uf-add-s-o" className={`${clsPrefix}-new-tab`} onClick={this.createNewTab} />
            {extraContent}

        );
      }
    }

    const contents = [
      React.cloneElement(this.tabBar, {
        clsPrefix,
        key: "tabBar",
        onKeyDown: this.onNavKeyDown,
        tabBarPosition,
        extraContent: tabBarExtraContent,
        onTabClick: this.onTabClick,
        panels: childrenWithClose.length > 0 ? childrenWithClose : children,
        activeKey: this.state.activeKey,
        tabIndex,
        onPrevClick: this.onPrevClick,
        onNextClick: this.onNextClick,
      }),
      React.cloneElement(renderTabContent(), {
        clsPrefix,
        tabBarPosition,
          animated,
        activeKey: this.state.activeKey,
        destroyInactiveTabPane: props.destroyInactiveTabPane,
        children: props.children,
          // style: { height: '100%' },
        onChange: this.setActiveKey,
        key: "tabContent"
      })
    ];
    if (tabBarPosition === "bottom") {
      contents.reverse();
    }
    return (

        {contents}

    );
  }
});

Tabs.TabPane = TabPane;

export  {Tabs};
<|endoftext|>


<commit_before>var paintr = paintr || {};

paintr.pen_color = "#EEEEE";

paintr.drawRect = function() {
  paintr.toggleMode();
  var is_drawing = false;
  var line;

  paintr.canvas.on('mouse:down', function(e) {
    var mouse_pos = paintr.canvas.getPointer(e.e);
    is_drawing = true;
    line = new fabric.Line([mouse_pos.x, mouse_pos.y, mouse_pos.x, mouse_pos.y], {
      strokeWidth: 5,
      fill: paintr.pen_color,
      stroke: paintr.pen_color,
      originX: 'center',
      originY: 'center'
    });
    paintr.canvas.add(line);
  });

  paintr.canvas.on('mouse:move', function(e) {
    if (!is_drawing) return;
    var mouse_pos = paintr.canvas.getPointer(e.e);
    line.set({ x2: mouse_pos.x, y2: mouse_pos.y });
    paintr.canvas.renderAll();
  });

  paintr.canvas.on('mouse:up', function(e) {
    is_drawing = false;
  });
}

paintr.toggleMode = function() {
  paintr.canvas.off('mouse:down');
  paintr.canvas.off('mouse:up');
  paintr.canvas.off('mouse:move');
}

paintr.drawFreehand = function() {
  paintr.toggleMode();
}

window.onload = function() {
  paintr.canvas = new fabric.Canvas('canvas');
  document.getElementById('line').addEventListener('click', paintr.drawRect);
  document.getElementById('freehand').addEventListener('click', paintr.drawFreehand);
}<commit_msg>Added freehand and select modes<commit_after>var paintr = paintr || {};

paintr.pen_color = "#EEEEE";

paintr.drawRect = function() {
  paintr.toggleMode();
  var is_drawing = false;
  var line;

  paintr.canvas.on('mouse:down', function(e) {
    var mouse_pos = paintr.canvas.getPointer(e.e);
    is_drawing = true;
    line = new fabric.Line([mouse_pos.x, mouse_pos.y, mouse_pos.x, mouse_pos.y], {
      strokeWidth: 5,
      fill: paintr.pen_color,
      stroke: paintr.pen_color,
      originX: 'center',
      originY: 'center',
      evented: true
    });
    paintr.canvas.add(line);
  });

  paintr.canvas.on('mouse:move', function(e) {
    if (!is_drawing) return;
    var mouse_pos = paintr.canvas.getPointer(e.e);
    line.set({ x2: mouse_pos.x, y2: mouse_pos.y });
    paintr.canvas.renderAll();
  });

  paintr.canvas.on('mouse:up', function(e) {
    is_drawing = false;
  });
}

paintr.drawFreehand = function() {
  paintr.toggleMode();
  paintr.canvas.isDrawingMode = true;
  paintr.canvas.freeDrawingLineWidth = 100;
  paintr.canvas.renderAll();
}

paintr.select = function() {
  paintr.toggleMode();
  paintr.canvas.selection = true;
  paintr.canvas.forEachObject(function(obj) {
    obj.selectable = true;
  });
  paintr.canvas.renderAll();
}

paintr.toggleMode = function() {
  paintr.canvas.off('mouse:down');
  paintr.canvas.off('mouse:up');
  paintr.canvas.off('mouse:move');
  paintr.canvas.isDrawingMode = false;
  paintr.canvas.selection = false;
  paintr.canvas.forEachObject(function(obj) {
    obj.selectable = false;
    console.log(obj.stateProperties);
  });
}

window.onload = function() {
  paintr.canvas = new fabric.Canvas('canvas', { selection: true });
  document.getElementById('line').addEventListener('click', paintr.drawRect);
  document.getElementById('freehand').addEventListener('click', paintr.drawFreehand);
  document.getElementById('select').addEventListener('click', paintr.select);
}<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var argv = require('yargs').argv;
var gulp = require('gulp');
var argv = require('yargs').argv;
var gutil = require('gulp-util');
var connect = require('gulp-connect');
var webpack = require('webpack-stream');
var uglify = require('gulp-uglify');
var clean = require('gulp-clean');
var karma = require('gulp-karma');
var opens = require('open');
var webpackConfig = require('./webpack.conf.js');
var helpers = require('./gulpHelpers');
var del = require('del');
var gulpJsdoc2md = require('gulp-jsdoc-to-markdown');
var concat = require('gulp-concat');
var header = require('gulp-header');
var footer = require('gulp-footer');
var zip = require('gulp-zip');
var replace = require('gulp-replace');
var shell = require('gulp-shell');
var optimizejs = require('gulp-optimize-js');
var eslint = require('gulp-eslint');
var gulpif = require('gulp-if');
var sourcemaps = require('gulp-sourcemaps');
var fs = require('fs');

var CI_MODE = process.env.NODE_ENV === 'ci';
var prebid = require('./package.json');
var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10);
var packageNameVersion = prebid.name + '_' + prebid.version;
var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n';
var analyticsDirectory = '../analytics';
var port = 9999;

// Tasks
gulp.task('default', ['clean', 'webpack']);

gulp.task('serve', ['clean', 'lint', 'build-bundle-dev', 'watch', 'test']);

gulp.task('serve-nw', ['clean', 'lint', 'devpack', 'webpack', 'watch', 'e2etest']);

gulp.task('run-tests', ['clean', 'lint', 'webpack', 'test']);

gulp.task('build', ['build-bundle-prod']);

gulp.task('clean', function () {
  return gulp.src(['build'], {
      read: false
    })
    .pipe(clean());
});

function bundle(dev) {
  var modules = helpers.getArgModules(),
      allModules = helpers.getModuleNames(modules);

  if(modules.length === 0) {
    modules = allModules;
  } else {
    var diff = _.difference(modules, allModules);
    if(diff.length !== 0) {
      throw new gutil.PluginError({
        plugin: 'bundle',
        message: 'invalid modules: ' + diff.join(', ')
      });
    }
  }

  var entries = [helpers.getBuiltPrebidCoreFile(dev)].concat(helpers.getBuiltModules(dev, modules));

  gutil.log('Concatenating files:\n', entries);
  gutil.log('Appending ' + prebid.globalVarName + '.processQueue();');

  return gulp.src(
      entries
    )
    .pipe(gulpif(dev, sourcemaps.init({loadMaps: true})))
    .pipe(concat(argv.bundleName ? argv.bundleName : 'prebid.js'))
    .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', {
        global: prebid.globalVarName
      }
    )))
    .pipe(gulpif(dev, sourcemaps.write('.')))
    .pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist')));
}

gulp.task('build-bundle-dev', ['devpack'], bundle.bind(null, true));
gulp.task('build-bundle-prod', ['webpack'], bundle.bind(null, false));
gulp.task('bundle', bundle.bind(null, false)); // used for just concatenating pre-built files with no build step

gulp.task('devpack', ['clean'], function () {
  webpackConfig.devtool = 'source-map';
  var externalModules = helpers.getArgModules();

  const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
  const moduleSources = helpers.getModulePaths(externalModules);

  return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
    .pipe(helpers.nameModules(externalModules))
    .pipe(webpack(webpackConfig))
    .pipe(replace('$prebid.version$', prebid.version))
    .pipe(gulp.dest('build/dev'))
    .pipe(connect.reload());
});

gulp.task('webpack', ['clean'], function () {

  // change output filename if argument --tag given
  if (argv.tag && argv.tag.length) {
    webpackConfig.output.filename = 'prebid.' + argv.tag + '.js';
  }

  webpackConfig.devtool = null;

  var externalModules = helpers.getArgModules();

  const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
  const moduleSources = helpers.getModulePaths(externalModules);

  return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
    .pipe(helpers.nameModules(externalModules))
    .pipe(webpack(webpackConfig))
    .pipe(replace('$prebid.version$', prebid.version))
    .pipe(uglify())
    .pipe(gulpif(file => file.basename === 'prebid.js', header(banner, { prebid: prebid })))
    .pipe(optimizejs())
    .pipe(gulp.dest('build/dist'))
    .pipe(connect.reload());
});

//zip up for release
gulp.task('zip', ['clean', 'webpack'], function () {
  return gulp.src(['build/dist/*', 'integrationExamples/gpt/*'])
    .pipe(zip(packageNameVersion + '.zip'))
    .pipe(gulp.dest('./'));
});

// Karma Continuous Testing
// Pass your browsers by using --browsers=chrome,firefox,ie9
// Run CI by passing --watch
gulp.task('test', ['clean'], function () {
  var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome'];
  var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase);

  if (process.env.TRAVIS) {
    browserArgs = ['Chrome_travis_ci'];
  }

  if (argv.browserstack) {
    browserArgs = [
      'bs_ie_13_windows_10',
      'bs_ie_11_windows_10',
      'bs_firefox_46_windows_10',
      'bs_chrome_51_windows_10',
      'bs_ie_11_windows_8.1',
      'bs_firefox_46_windows_8.1',
      'bs_chrome_51_windows_8.1',
      'bs_ie_10_windows_8',
      'bs_firefox_46_windows_8',
      'bs_chrome_51_windows_8',
      'bs_ie_11_windows_7',
      'bs_ie_10_windows_7',
      'bs_ie_9_windows_7',
      'bs_firefox_46_windows_7',
      'bs_chrome_51_windows_7',
      'bs_safari_9.1_mac_elcapitan',
      'bs_firefox_46_mac_elcapitan',
      'bs_chrome_51_mac_elcapitan',
      'bs_safari_8_mac_yosemite',
      'bs_firefox_46_mac_yosemite',
      'bs_chrome_51_mac_yosemite',
      'bs_safari_7.1_mac_mavericks',
      'bs_firefox_46_mac_mavericks',
      'bs_chrome_49_mac_mavericks',
      'bs_ios_7',
      'bs_ios_8',
      'bs_ios_9',
    ];
  }

  return gulp.src('lookAtKarmaConfJS')
    .pipe(karma({
      browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers,
      configFile: 'karma.conf.js',
      action: (argv.watch) ? 'watch' : 'run'
    }));
});

// Small task to load coverage reports in the browser
gulp.task('coverage', function (done) {
  var coveragePort = 1999;

  connect.server({
    port: 1999,
    root: 'build/coverage',
    livereload: false
  });
  opens('http://localhost:' + coveragePort + '/coverage/');
  done();
});

gulp.task('coveralls', ['test'], function() { // 2nd arg is a dependency: 'test' must be finished
  // first send results of istanbul's test coverage to coveralls.io.
  return gulp.src('gulpfile.js', { read: false }) // You have to give it a file, but you don't
  // have to read it.
    .pipe(shell('cat build/coverage/lcov/lcov.info | node_modules/coveralls/bin/coveralls.js'));
});

// Watch Task with Live Reload
gulp.task('watch', function () {

  gulp.watch([
    'src/**/*.js',
    'modules/**/*.js',
    'test/spec/**/*.js',
    '!test/spec/loaders/**/*.js'
  ], ['lint', 'build-bundle-dev', 'test']);
  gulp.watch([
    'loaders/**/*.js',
    'test/spec/loaders/**/*.js'
  ], ['lint']);
  gulp.watch(['integrationExamples/gpt/*.html'], ['test']);
  connect.server({
    https: argv.https,
    port: port,
    root: './',
    livereload: true
  });
});

gulp.task('lint', () => {
  return gulp.src(['src/**/*.js', 'test/**/*.js'])
    .pipe(eslint())
    .pipe(eslint.format('stylish'))
    .pipe(eslint.failAfterError());
});

gulp.task('clean-docs', function () {
  del(['docs']);
});

gulp.task('docs', ['clean-docs'], function () {
  return gulp.src('src/prebid.js')
    .pipe(concat('readme.md'))
    .pipe(gulpJsdoc2md())
    .on('error', function (err) {
      gutil.log('jsdoc2md failed:', err.message);
    })
    .pipe(gulp.dest('docs'));
});

gulp.task('e2etest', function() {
  var cmdQueue = [];
  if(argv.browserstack) {
    var browsers = require('./browsers.json');
    delete browsers['bs_ie_9_windows_7'];

    var cmdStr = ' --config nightwatch.conf.js';
    if (argv.group) {
      cmdStr = cmdStr + ' --group ' + argv.group;
    }
    cmdStr = cmdStr + ' --reporter ./test/spec/e2e/custom-reporter/pbjs-html-reporter.js';

    var startWith = 'bs';

    Object.keys(browsers).filter(function(v){
      return v.substring(0, startWith.length) === startWith && browsers[v].browser !== 'iphone';
    }).map(function(v,i,arr) {
      var newArr = (i%2 === 0) ? arr.slice(i,i+2) : null;
      if(newArr) {
        var cmd = 'nightwatch --env ' + newArr.join(',') + cmdStr;
        cmdQueue.push(cmd);
      }
    });
  }

  return gulp.src('')
    .pipe(shell(cmdQueue.join(';')));
});

gulp.task('e2etest-report', function() {
  var reportPort = 9010;
  var targetDestinationDir = './e2etest-report';
  helpers.createEnd2EndTestReport(targetDestinationDir);
  connect.server({
    port: reportPort,
    root: './',
    livereload: true
  });

  setTimeout(function() {
    opens('http://localhost:' + reportPort + '/' + targetDestinationDir.slice(2) + '/results.html');
  }, 5000);

});
<commit_msg>Clean up gulp task dependencies, fixes #1333 and #1334 (#1336)<commit_after>'use strict';

var _ = require('lodash');
var argv = require('yargs').argv;
var gulp = require('gulp');
var argv = require('yargs').argv;
var gutil = require('gulp-util');
var connect = require('gulp-connect');
var webpack = require('webpack-stream');
var uglify = require('gulp-uglify');
var clean = require('gulp-clean');
var karma = require('gulp-karma');
var opens = require('open');
var webpackConfig = require('./webpack.conf.js');
var helpers = require('./gulpHelpers');
var del = require('del');
var gulpJsdoc2md = require('gulp-jsdoc-to-markdown');
var concat = require('gulp-concat');
var header = require('gulp-header');
var footer = require('gulp-footer');
var zip = require('gulp-zip');
var replace = require('gulp-replace');
var shell = require('gulp-shell');
var optimizejs = require('gulp-optimize-js');
var eslint = require('gulp-eslint');
var gulpif = require('gulp-if');
var sourcemaps = require('gulp-sourcemaps');
var fs = require('fs');

var CI_MODE = process.env.NODE_ENV === 'ci';
var prebid = require('./package.json');
var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10);
var packageNameVersion = prebid.name + '_' + prebid.version;
var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n';
var analyticsDirectory = '../analytics';
var port = 9999;

// Tasks
gulp.task('default', ['webpack']);

gulp.task('serve', ['lint', 'build-bundle-dev', 'watch', 'test']);

gulp.task('serve-nw', ['lint', 'watch', 'e2etest']);

gulp.task('run-tests', ['lint', 'test']);

gulp.task('build', ['build-bundle-prod']);

gulp.task('clean', function () {
  return gulp.src(['build'], {
      read: false
    })
    .pipe(clean());
});

function bundle(dev) {
  var modules = helpers.getArgModules(),
      allModules = helpers.getModuleNames(modules);

  if(modules.length === 0) {
    modules = allModules;
  } else {
    var diff = _.difference(modules, allModules);
    if(diff.length !== 0) {
      throw new gutil.PluginError({
        plugin: 'bundle',
        message: 'invalid modules: ' + diff.join(', ')
      });
    }
  }

  var entries = [helpers.getBuiltPrebidCoreFile(dev)].concat(helpers.getBuiltModules(dev, modules));

  gutil.log('Concatenating files:\n', entries);
  gutil.log('Appending ' + prebid.globalVarName + '.processQueue();');

  return gulp.src(
      entries
    )
    .pipe(gulpif(dev, sourcemaps.init({loadMaps: true})))
    .pipe(concat(argv.bundleName ? argv.bundleName : 'prebid.js'))
    .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', {
        global: prebid.globalVarName
      }
    )))
    .pipe(gulpif(dev, sourcemaps.write('.')))
    .pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist')));
}

gulp.task('build-bundle-dev', ['devpack'], bundle.bind(null, true));
gulp.task('build-bundle-prod', ['webpack'], bundle.bind(null, false));
gulp.task('bundle', bundle.bind(null, false)); // used for just concatenating pre-built files with no build step

gulp.task('devpack', ['clean'], function () {
  webpackConfig.devtool = 'source-map';
  var externalModules = helpers.getArgModules();

  const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
  const moduleSources = helpers.getModulePaths(externalModules);

  return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
    .pipe(helpers.nameModules(externalModules))
    .pipe(webpack(webpackConfig))
    .pipe(replace('$prebid.version$', prebid.version))
    .pipe(gulp.dest('build/dev'))
    .pipe(connect.reload());
});

gulp.task('webpack', ['clean'], function () {

  // change output filename if argument --tag given
  if (argv.tag && argv.tag.length) {
    webpackConfig.output.filename = 'prebid.' + argv.tag + '.js';
  }

  webpackConfig.devtool = null;

  var externalModules = helpers.getArgModules();

  const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
  const moduleSources = helpers.getModulePaths(externalModules);

  return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
    .pipe(helpers.nameModules(externalModules))
    .pipe(webpack(webpackConfig))
    .pipe(replace('$prebid.version$', prebid.version))
    .pipe(uglify())
    .pipe(gulpif(file => file.basename === 'prebid.js', header(banner, { prebid: prebid })))
    .pipe(optimizejs())
    .pipe(gulp.dest('build/dist'))
    .pipe(connect.reload());
});

//zip up for release
gulp.task('zip', ['webpack'], function () {
  return gulp.src(['build/dist/*', 'integrationExamples/gpt/*'])
    .pipe(zip(packageNameVersion + '.zip'))
    .pipe(gulp.dest('./'));
});

// Karma Continuous Testing
// Pass your browsers by using --browsers=chrome,firefox,ie9
// Run CI by passing --watch
gulp.task('test', ['clean'], function () {
  var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome'];
  var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase);

  if (process.env.TRAVIS) {
    browserArgs = ['Chrome_travis_ci'];
  }

  if (argv.browserstack) {
    browserArgs = [
      'bs_ie_13_windows_10',
      'bs_ie_11_windows_10',
      'bs_firefox_46_windows_10',
      'bs_chrome_51_windows_10',
      'bs_ie_11_windows_8.1',
      'bs_firefox_46_windows_8.1',
      'bs_chrome_51_windows_8.1',
      'bs_ie_10_windows_8',
      'bs_firefox_46_windows_8',
      'bs_chrome_51_windows_8',
      'bs_ie_11_windows_7',
      'bs_ie_10_windows_7',
      'bs_ie_9_windows_7',
      'bs_firefox_46_windows_7',
      'bs_chrome_51_windows_7',
      'bs_safari_9.1_mac_elcapitan',
      'bs_firefox_46_mac_elcapitan',
      'bs_chrome_51_mac_elcapitan',
      'bs_safari_8_mac_yosemite',
      'bs_firefox_46_mac_yosemite',
      'bs_chrome_51_mac_yosemite',
      'bs_safari_7.1_mac_mavericks',
      'bs_firefox_46_mac_mavericks',
      'bs_chrome_49_mac_mavericks',
      'bs_ios_7',
      'bs_ios_8',
      'bs_ios_9',
    ];
  }

  return gulp.src('lookAtKarmaConfJS')
    .pipe(karma({
      browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers,
      configFile: 'karma.conf.js',
      action: (argv.watch) ? 'watch' : 'run'
    }));
});

// Small task to load coverage reports in the browser
gulp.task('coverage', function (done) {
  var coveragePort = 1999;

  connect.server({
    port: 1999,
    root: 'build/coverage',
    livereload: false
  });
  opens('http://localhost:' + coveragePort + '/coverage/');
  done();
});

gulp.task('coveralls', ['test'], function() { // 2nd arg is a dependency: 'test' must be finished
  // first send results of istanbul's test coverage to coveralls.io.
  return gulp.src('gulpfile.js', { read: false }) // You have to give it a file, but you don't
  // have to read it.
    .pipe(shell('cat build/coverage/lcov/lcov.info | node_modules/coveralls/bin/coveralls.js'));
});

// Watch Task with Live Reload
gulp.task('watch', function () {

  gulp.watch([
    'src/**/*.js',
    'modules/**/*.js',
    'test/spec/**/*.js',
    '!test/spec/loaders/**/*.js'
  ], ['lint', 'build-bundle-dev', 'test']);
  gulp.watch([
    'loaders/**/*.js',
    'test/spec/loaders/**/*.js'
  ], ['lint']);
  connect.server({
    https: argv.https,
    port: port,
    root: './',
    livereload: true
  });
});

gulp.task('lint', () => {
  return gulp.src(['src/**/*.js', 'test/**/*.js'])
    .pipe(eslint())
    .pipe(eslint.format('stylish'))
    .pipe(eslint.failAfterError());
});

gulp.task('clean-docs', function () {
  del(['docs']);
});

gulp.task('docs', ['clean-docs'], function () {
  return gulp.src('src/prebid.js')
    .pipe(concat('readme.md'))
    .pipe(gulpJsdoc2md())
    .on('error', function (err) {
      gutil.log('jsdoc2md failed:', err.message);
    })
    .pipe(gulp.dest('docs'));
});

gulp.task('e2etest', ['devpack', 'webpack'], function() {
  var cmdQueue = [];
  if(argv.browserstack) {
    var browsers = require('./browsers.json');
    delete browsers['bs_ie_9_windows_7'];

    var cmdStr = ' --config nightwatch.conf.js';
    if (argv.group) {
      cmdStr = cmdStr + ' --group ' + argv.group;
    }
    cmdStr = cmdStr + ' --reporter ./test/spec/e2e/custom-reporter/pbjs-html-reporter.js';

    var startWith = 'bs';

    Object.keys(browsers).filter(function(v){
      return v.substring(0, startWith.length) === startWith && browsers[v].browser !== 'iphone';
    }).map(function(v,i,arr) {
      var newArr = (i%2 === 0) ? arr.slice(i,i+2) : null;
      if(newArr) {
        var cmd = 'nightwatch --env ' + newArr.join(',') + cmdStr;
        cmdQueue.push(cmd);
      }
    });
  }

  return gulp.src('')
    .pipe(shell(cmdQueue.join(';')));
});

gulp.task('e2etest-report', function() {
  var reportPort = 9010;
  var targetDestinationDir = './e2etest-report';
  helpers.createEnd2EndTestReport(targetDestinationDir);
  connect.server({
    port: reportPort,
    root: './',
    livereload: true
  });

  setTimeout(function() {
    opens('http://localhost:' + reportPort + '/' + targetDestinationDir.slice(2) + '/results.html');
  }, 5000);

});
<|endoftext|>


<commit_before>/* ************************************************************************

    License: MIT Licence

    Dependencies : [
        a.js
        plugin/translate.js
    ]

    Events : []

    Description:
        Manipulate HTML form by with a simple system.

************************************************************************ */

/**
 * Manipulate HTML form by with a simple system.
 *
 * Examples: here
 *
 * @class form
 * @static
 * @namespace a
*/
a.form = (function() {
    'use strict';

    // HTML/HTML5 input type allowed
    var typePatternList  = ['text', 'search', 'url',
                                    'tel', 'email', 'password'],
        minMaxStepList   = ['number', 'range', 'date',
                                    'datetime', 'datetime-local',
                                    'month', 'time', 'week'],
        typeRequiredList = typePatternList.concat(minMaxStepList,
                                    ['number', 'checkbox', 'radio', 'file']),
        typeMultipleList = ['email', 'file'],
        typeList = minMaxStepList.concat(typePatternList,
                                    ['color', 'checkbox', 'file',
                                    'hidden', 'radio']);

    /**
     * Get the field key from given input.
     *
     * @method getFieldKey
     * @private
     *
     * @param e {DOMElement}                The element o search value inside
     * @return {String}                     The value found
    */
    function getFieldKey(e) {
        var el   = a.dom.el(e),
            name = el.data('name');

        if(a.isNone(name) || name == '') {
            name = el.attribute('name');

            // Search the good attribute in case of problem
            if(a.isNone(name) || name === '') {
                name = el.attribute('id');

                // Should never appear... But we provide it in case of trouble
                if(a.isNone(name) || name === '') {
                    name = el.attribute('class');
                }
            }
        }

        return name;
    };

    /**
     * Get the field value for given input.
     *
     * @method getFieldValue
     * @private
     *
     * @param e {DOMElement}                The element to search value inside
     * @return {String}                     The value found
    */
    function getFieldValue(e) {
        var type    = e.type || '',
            tagName = e.tagName.toLowerCase();

        if(tagName === 'input' || tagName === 'textarea') {
            return (type === 'checkbox') ? e.checked : e.value;
        } else if(tagName === 'select') {
            if(e.options[e.selectedIndex]) {
                return e.options[e.selectedIndex].value
            }
            return null;
        }
    };

    /**
     * From a given dom, get the list of revelant elements inside.
     *
     * @method getFieldList
     * @private
     *
     * @param dom {a.dom}                   The dom element to search inside
     * @return {Array}                      The element list inside DOM
    */
    function getFieldList(dom) {
        // dom must be a a.dom element
        var elements = dom.tag(['input', 'textarea', 'select']).getElements();

        // We remove input who are not listed in typeList
        // LIKE: submit/reset should not appear in this list
        var i = elements.length;
        while(i--) {
            var el = elements[i];
            if(el.type &&
                    (  el.type == 'submit'
                    || el.type == 'reset'
                    || el.type == 'image'
                    ) ) {
                elements.splice(i, 1);
            }
        }

        // Now filtering is done, we can send back all elements
        return elements;
    };

    /**
     * Raise an error on input.
     *
     * @method validateError
     * @private
     *
     * @param el {DOMElement}               The element where comes from error
     * @param id {String}                   The element id/name/class
     * @param name {String | null}          The name (like min, pattern, ...)
     *                                      which is not valid, can be null
     * @param value {String | null}         The current input value
     *                                      (can be used as parameter)
     * @return {Object}                     A validate object with everything
     *                                      inside if possible
    */
    function validateError(el, id, name, value) {
        // First : we need to get error element and translate if possible
        var error = '';

        // Retrieve error tag
        if(!a.isNone(name) && name !== '') {
            error = el['data-error-' + name] || null;
        }
        if(a.isNone(error) || error === '') {
            error = el['data-error'] || null;
        }

        if(a.isNone(error) || error === '') {
            var errorMessage  = 'A data-error tag has not been setted for ';
                errorMessage += id + ' with value ' + value + 'n can\'t ';
                errorMessage += 'proceed error message';
            a.console.warn(errorMessage, 3);
        }

        // Translate error if possible
        error = a.translate.get(error, {
            name:  name,
            value: value
        });

        // Returning an object with all needed data inside
        return {
            el:    el,
            id:    id,
            error: error
        };
    };

    return {
        /**
         * Allow to skip HTML5 form-novalidate tag or not (boolean)
         *
         * @property skipNoValidate
         * @type Boolean
         * @default false
        */
        skipNoValidate: false,

        /**
         * Get the list of element stored into given form.
         *
         * @method get
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 The list of input tags existing
        */
        get: function(dom) {
            dom = a.dom.el(dom);
            var inputList  = getFieldList(dom),
                outputList = {};

            var i = inputList.length;
            while(i--) {
                var input = inputList[i];

                var name  = a.trim(getFieldKey(input)),
                    value = getFieldValue(input);

                // We don't continue if we don't find any data on element
                if(a.isNone(name) || !name) {
                    continue;
                }

                var parse = false;

                // We got a special case with input radio type
                if(!a.isNone(input) && input.type === 'radio') {
                    // Only checked one are validated
                    if(input.checked) {
                        parse = true;
                    }
                } else if(!a.isNone(input) && input.type === 'checkbox') {
                    parse = false;
                    outputList[name] = (input.checked) ? true: false;
                } else {
                    parse = true;
                }

                if(parse) {
                    // Name is a multiple value one (using [] at the end)
                    if(name.substr(name.length - 2) === '[]') {
                        if(!a.isArray(outputList[name])) {
                            outputList[name] = [];
                        }
                        value = (value) ? value: null;
                        outputList[name].push(value);
                    } else {
                       outputList[name] = (value) ? value: null;
                   }
                }
            };

            a.console.log('a.form.get: found element list:', 3);
            a.console.log(outputList, 3);
            return outputList;
        },

        /**
         * Validate a form
         * Note : multiple tester (email, file) is not supported
         * Note : date field (date, datetime, datetime-local,
         * month, time, week) are not supported
         * Note : tel/file field are not supported
         *
         * @method validate
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Array}                  An array with all errors listed
         *                                  inside, an empty array if there
         *                                  is no error to show
        */
        validate: function(dom) {
            dom = a.dom.el(dom);
            // On form tag, the "novalidate" allow to not validate a form
            if(this.skipNoValidate === false &&
                    !a.isNone(dom.get(0).novalidate)) {
                return [];
            }

            var inputList    = getFieldList(dom),
                // Store all errors appearing
                errorList    = [],
                // TODO : after todos are ended, remove this...
                allowedTypes = ['number', 'range', 'text', 'search',
                                        'url', 'email', 'password',
                                        'color', 'checkbox',
                                        'hidden', 'radio'],
                // Pretty basic url tester
                urlTester    = new RegExp(
                    '^[a-z]{2,}:\\/\\/([a-z0-9\\/\\.\\-_~+;:&=]{2,})$', 'gi'),
                // Pretty basic email tester
                emailTester  = new RegExp('^.{2,}@.*\\.[a-z0-9]{2,}$', 'gi'),
                colorTester  = new RegExp('^#([a-f]{3}|[a-f]{6})$', 'gi');

            /*
             * required : at least one char
                (text, search, url, tel, email, password, date, datetime,
                datetime-local, month, time, week, number, checkbox,
                radio, file)
             * pattern : a regex to test (Use title like a helper),
                (text, search, url, tel, email, password)
             * multiple : the user is allowed to enter more than one element
                (only for email, file)
             * min/max : min/max value
                (number, range, date, datetime, datetime-local,
                month, time, week)
             * step : multiplier
                (number, range, date, datetime, datetime-local,
                month, time, week)
            */
            var i = inputList.length;
            while(i--) {
                // Does only work for input tags
                var el      = inputList[i],
                    tagName = el.tagName.toLowerCase();

                // form novalidate : we must not validate
                // this element (including all select)
                if(tagName == 'select'
                    || !a.isNone(el.novalidate)) {
                    continue;
                }

                var type     = el.type,
                    name    = getFieldKey(el),
                    value    = el.value,

                    required = el.required,
                    pattern  = el.pattern,
                    multiple = el.multiple,
                    min      = el.min,
                    max      = el.max,
                    step     = el.step;

                // Double check float data
                min  = (a.isNone(min) || min == '')   ? null :
                                                            parseFloat(min);
                max  = (a.isNone(max) || max == '')   ? null :
                                                            parseFloat(max);
                step = (a.isNone(step) || step == '') ? null :
                                                            parseFloat(step);

                // Check input type does existing in allowed type list
                if(tagName == 'input'
                        && !a.contains(allowedTypes, type)
                        && !a.isNone(type)) {
                    var errorSupport =  'Type ' + type + ' for input ' + name;
                        errorSupport += ' not recognized or not supported';
                    a.console.warn(errorSupport, 3);
                    continue;
                }

                // Now checking type
                if( (type == 'number' || type == 'range')
                        && !a.isNumber(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'url' && !urlTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'email' && !emailTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'color' && !colorTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }

                // Required test
                if( required !== null
                    && a.contains(typeRequiredList, type)
                    && (value === '' || a.isNone(value)) ) {
                    errorList.push(validateError(el, name, 'required', value));
                    continue;
                }

                // Pattern test
                if( pattern !== null
                     && (tagName === "textarea"
                        ||(a.contains(typePatternList, type))
                        || a.isNone(type)
                        )
                ) {
                    var reg = new RegExp(pattern);
                    if(!reg.test(value)) {
                        errorList.push(validateError(
                                            el, name, 'pattern', value));
                        continue;
                    }
                }

                // Min/max/step test
                if( (min !== null || max != null || step != null)
                    && a.contains(minMaxStepList, type) ) {

                    var pval = parseFloat(value);
                    if( min !== null && pval < min ) {
                        errorList.push(validateError(el, name, 'min', value));
                        continue;
                    }
                    if( max !== null && pval > max ) {
                        errorList.push(validateError(el, name, 'max', value));
                        continue;
                    }
                    if( step !== null && pval % step !== 0 ) {
                        errorList.push(validateError(el, name, 'step', value));
                        continue;
                    }
                }
            }

            a.console.log('a.form.validate: found error list:', 3);
            a.console.log(errorList, 3);
            return errorList;
        },

        /**
         * Validate and get the form content.
         *
         * @method validateAndGet
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 An object with error (boolean),
         *                                  errorList (Array)
         *                                  and contentList (Array)
        */
        validateAndGet: function(dom) {
            var obj = {
                errorList   : this.validate(dom),
                error       : false,
                contentList : this.get(dom)
            };
            if(obj.errorList.length > 0) {
                obj.error = true;
            }
            return obj;
        }
    };

})();<commit_msg>Removing old todo<commit_after>/* ************************************************************************

    License: MIT Licence

    Dependencies : [
        a.js
        plugin/translate.js
    ]

    Events : []

    Description:
        Manipulate HTML form by with a simple system.

************************************************************************ */

/**
 * Manipulate HTML form by with a simple system.
 *
 * Examples: here
 *
 * @class form
 * @static
 * @namespace a
*/
a.form = (function() {
    'use strict';

    // HTML/HTML5 input type allowed
    var typePatternList  = ['text', 'search', 'url',
                                    'tel', 'email', 'password'],
        minMaxStepList   = ['number', 'range', 'date',
                                    'datetime', 'datetime-local',
                                    'month', 'time', 'week'],
        typeRequiredList = typePatternList.concat(minMaxStepList,
                                    ['number', 'checkbox', 'radio', 'file']),
        typeMultipleList = ['email', 'file'],
        typeList = minMaxStepList.concat(typePatternList,
                                    ['color', 'checkbox', 'file',
                                    'hidden', 'radio']);

    /**
     * Get the field key from given input.
     *
     * @method getFieldKey
     * @private
     *
     * @param e {DOMElement}                The element o search value inside
     * @return {String}                     The value found
    */
    function getFieldKey(e) {
        var el   = a.dom.el(e),
            name = el.data('name');

        if(a.isNone(name) || name == '') {
            name = el.attribute('name');

            // Search the good attribute in case of problem
            if(a.isNone(name) || name === '') {
                name = el.attribute('id');

                // Should never appear... But we provide it in case of trouble
                if(a.isNone(name) || name === '') {
                    name = el.attribute('class');
                }
            }
        }

        return name;
    };

    /**
     * Get the field value for given input.
     *
     * @method getFieldValue
     * @private
     *
     * @param e {DOMElement}                The element to search value inside
     * @return {String}                     The value found
    */
    function getFieldValue(e) {
        var type    = e.type || '',
            tagName = e.tagName.toLowerCase();

        if(tagName === 'input' || tagName === 'textarea') {
            return (type === 'checkbox') ? e.checked : e.value;
        } else if(tagName === 'select') {
            if(e.options[e.selectedIndex]) {
                return e.options[e.selectedIndex].value
            }
            return null;
        }
    };

    /**
     * From a given dom, get the list of revelant elements inside.
     *
     * @method getFieldList
     * @private
     *
     * @param dom {a.dom}                   The dom element to search inside
     * @return {Array}                      The element list inside DOM
    */
    function getFieldList(dom) {
        // dom must be a a.dom element
        var elements = dom.tag(['input', 'textarea', 'select']).getElements();

        // We remove input who are not listed in typeList
        // LIKE: submit/reset should not appear in this list
        var i = elements.length;
        while(i--) {
            var el = elements[i];
            if(el.type &&
                    (  el.type == 'submit'
                    || el.type == 'reset'
                    || el.type == 'image'
                    ) ) {
                elements.splice(i, 1);
            }
        }

        // Now filtering is done, we can send back all elements
        return elements;
    };

    /**
     * Raise an error on input.
     *
     * @method validateError
     * @private
     *
     * @param el {DOMElement}               The element where comes from error
     * @param id {String}                   The element id/name/class
     * @param name {String | null}          The name (like min, pattern, ...)
     *                                      which is not valid, can be null
     * @param value {String | null}         The current input value
     *                                      (can be used as parameter)
     * @return {Object}                     A validate object with everything
     *                                      inside if possible
    */
    function validateError(el, id, name, value) {
        // First : we need to get error element and translate if possible
        var error = '';

        // Retrieve error tag
        if(!a.isNone(name) && name !== '') {
            error = el['data-error-' + name] || null;
        }
        if(a.isNone(error) || error === '') {
            error = el['data-error'] || null;
        }

        if(a.isNone(error) || error === '') {
            var errorMessage  = 'A data-error tag has not been setted for ';
                errorMessage += id + ' with value ' + value + 'n can\'t ';
                errorMessage += 'proceed error message';
            a.console.warn(errorMessage, 3);
        }

        // Translate error if possible
        error = a.translate.get(error, {
            name:  name,
            value: value
        });

        // Returning an object with all needed data inside
        return {
            el:    el,
            id:    id,
            error: error
        };
    };

    return {
        /**
         * Allow to skip HTML5 form-novalidate tag or not (boolean)
         *
         * @property skipNoValidate
         * @type Boolean
         * @default false
        */
        skipNoValidate: false,

        /**
         * Get the list of element stored into given form.
         *
         * @method get
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 The list of input tags existing
        */
        get: function(dom) {
            dom = a.dom.el(dom);
            var inputList  = getFieldList(dom),
                outputList = {};

            var i = inputList.length;
            while(i--) {
                var input = inputList[i];

                var name  = a.trim(getFieldKey(input)),
                    value = getFieldValue(input);

                // We don't continue if we don't find any data on element
                if(a.isNone(name) || !name) {
                    continue;
                }

                var parse = false;

                // We got a special case with input radio type
                if(!a.isNone(input) && input.type === 'radio') {
                    // Only checked one are validated
                    if(input.checked) {
                        parse = true;
                    }
                } else if(!a.isNone(input) && input.type === 'checkbox') {
                    parse = false;
                    outputList[name] = (input.checked) ? true: false;
                } else {
                    parse = true;
                }

                if(parse) {
                    // Name is a multiple value one (using [] at the end)
                    if(name.substr(name.length - 2) === '[]') {
                        if(!a.isArray(outputList[name])) {
                            outputList[name] = [];
                        }
                        value = (value) ? value: null;
                        outputList[name].push(value);
                    } else {
                       outputList[name] = (value) ? value: null;
                   }
                }
            };

            a.console.log('a.form.get: found element list:', 3);
            a.console.log(outputList, 3);
            return outputList;
        },

        /**
         * Validate a form
         * Note : multiple tester (email, file) is not supported
         * Note : date field (date, datetime, datetime-local,
         * month, time, week) are not supported
         * Note : tel/file field are not supported
         *
         * @method validate
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Array}                  An array with all errors listed
         *                                  inside, an empty array if there
         *                                  is no error to show
        */
        validate: function(dom) {
            dom = a.dom.el(dom);
            // On form tag, the "novalidate" allow to not validate a form
            if(this.skipNoValidate === false &&
                    !a.isNone(dom.get(0).novalidate)) {
                return [];
            }

            var inputList    = getFieldList(dom),
                // Store all errors appearing
                errorList    = [],
                allowedTypes = ['number', 'range', 'text', 'search',
                                        'url', 'email', 'password',
                                        'color', 'checkbox',
                                        'hidden', 'radio'],
                // Pretty basic url tester
                urlTester    = new RegExp(
                    '^[a-z]{2,}:\\/\\/([a-z0-9\\/\\.\\-_~+;:&=]{2,})$', 'gi'),
                // Pretty basic email tester
                emailTester  = new RegExp('^.{2,}@.*\\.[a-z0-9]{2,}$', 'gi'),
                colorTester  = new RegExp('^#([a-f]{3}|[a-f]{6})$', 'gi');

            /*
             * required : at least one char
                (text, search, url, tel, email, password, date, datetime,
                datetime-local, month, time, week, number, checkbox,
                radio, file)
             * pattern : a regex to test (Use title like a helper),
                (text, search, url, tel, email, password)
             * multiple : the user is allowed to enter more than one element
                (only for email, file)
             * min/max : min/max value
                (number, range, date, datetime, datetime-local,
                month, time, week)
             * step : multiplier
                (number, range, date, datetime, datetime-local,
                month, time, week)
            */
            var i = inputList.length;
            while(i--) {
                // Does only work for input tags
                var el      = inputList[i],
                    tagName = el.tagName.toLowerCase();

                // form novalidate : we must not validate
                // this element (including all select)
                if(tagName == 'select'
                    || !a.isNone(el.novalidate)) {
                    continue;
                }

                var type     = el.type,
                    name    = getFieldKey(el),
                    value    = el.value,

                    required = el.required,
                    pattern  = el.pattern,
                    multiple = el.multiple,
                    min      = el.min,
                    max      = el.max,
                    step     = el.step;

                // Double check float data
                min  = (a.isNone(min) || min == '')   ? null :
                                                            parseFloat(min);
                max  = (a.isNone(max) || max == '')   ? null :
                                                            parseFloat(max);
                step = (a.isNone(step) || step == '') ? null :
                                                            parseFloat(step);

                // Check input type does existing in allowed type list
                if(tagName == 'input'
                        && !a.contains(allowedTypes, type)
                        && !a.isNone(type)) {
                    var errorSupport =  'Type ' + type + ' for input ' + name;
                        errorSupport += ' not recognized or not supported';
                    a.console.warn(errorSupport, 3);
                    continue;
                }

                // Now checking type
                if( (type == 'number' || type == 'range')
                        && !a.isNumber(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'url' && !urlTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'email' && !emailTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'color' && !colorTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }

                // Required test
                if( required !== null
                    && a.contains(typeRequiredList, type)
                    && (value === '' || a.isNone(value)) ) {
                    errorList.push(validateError(el, name, 'required', value));
                    continue;
                }

                // Pattern test
                if( pattern !== null
                     && (tagName === "textarea"
                        ||(a.contains(typePatternList, type))
                        || a.isNone(type)
                        )
                ) {
                    var reg = new RegExp(pattern);
                    if(!reg.test(value)) {
                        errorList.push(validateError(
                                            el, name, 'pattern', value));
                        continue;
                    }
                }

                // Min/max/step test
                if( (min !== null || max != null || step != null)
                    && a.contains(minMaxStepList, type) ) {

                    var pval = parseFloat(value);
                    if( min !== null && pval < min ) {
                        errorList.push(validateError(el, name, 'min', value));
                        continue;
                    }
                    if( max !== null && pval > max ) {
                        errorList.push(validateError(el, name, 'max', value));
                        continue;
                    }
                    if( step !== null && pval % step !== 0 ) {
                        errorList.push(validateError(el, name, 'step', value));
                        continue;
                    }
                }
            }

            a.console.log('a.form.validate: found error list:', 3);
            a.console.log(errorList, 3);
            return errorList;
        },

        /**
         * Validate and get the form content.
         *
         * @method validateAndGet
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 An object with error (boolean),
         *                                  errorList (Array)
         *                                  and contentList (Array)
        */
        validateAndGet: function(dom) {
            var obj = {
                errorList   : this.validate(dom),
                error       : false,
                contentList : this.get(dom)
            };
            if(obj.errorList.length > 0) {
                obj.error = true;
            }
            return obj;
        }
    };

})();<|endoftext|>


<commit_before>/* global Handlebars */
/*
 * jQuery-handlebars
 * https://github.com/softwarespot/jquery-handlebars
 * Author: softwarespot
 * Licensed under the MIT license
 * Version: 0.1.0-beta
 */
; (function ($, undefined) {

    // Initial idea from: http://blog.teamtreehouse.com/handlebars-js-part-3-tips-and-tricks

    // Plugin Logic
    $.fn.extend({

        // data doubles up as options when the action is either 'clear' or 'remove'
        handlebars: function (action, template, dataOrOptions, options) {

            // jQuery object reference for this. Only select the first selector of the collection
            var $this = $(this).first(),

                // The following was taken from sizzle.js, URL: https://github.com/jquery/sizzle/blob/master/dist/sizzle.js

                // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
                identifier = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+',

                rIdentifier = new RegExp('^#' + identifier + '$');

                // Store whether the template is valid or not
                isTemplate = typeof template === 'string' && rIdentifier.test(template);

            // Debugging only
            console.log('jQuery-handlebars: Is a template? [%s, %s]', template, isTemplate);

            // These actions don't require any pre-processing
            if (typeof action === 'string') {

                // If a 'get' action is provided, then get the template(s)
                // The template string if not defined will return all template(s)
                if (/^FIND|GET$/i.test(action)) {

                    // The include parameter will be true, if the template is a string and valid anchor
                    return getTemplate($this, template, isTemplate);

                // If a 'compiled' action is provided, then return the compiled object literal
                } else if (/^COMPILED$/i.test(action)) {

                    return compiled;

                }

            }

            // Set our options from the defaults, overriding with the
            // parameter we pass into this function
            options = $.extend({}, $.fn.handlebars.options, options);

            // START: Sanitize the options

            // Check if the option type is a string and valid
            if (typeof options.type === 'string' && /^APPEND|COMPILED|RAW$/i.test(options.type)) {

                // Set to uppercase
                options.type = options.type.toUpperCase();

            } else {

                // Otherwise default to 'append' i.e. if null or simply invalid
                options.type = Type.APPEND;

            }

            // If the removal type is a string and valid, then set to uppercase
            if (typeof options.remove_type === 'string' && /^ALL|NONE|SAME$/i.test(options.remove_type)) {

                // Set to uppercase
                options.remove_type = options.remove_type.toUpperCase();

            } else {

                // Otherwise default to 'none' i.e. if null or simply is invalid
                options.remove_type = Remove.NONE;

            }

            // END: Sanitize the options

            // If a 'clear' action is provided, then remove the template from the content element
            if (/^CLEAR|REMOVE$/i.test(action)) {

                // Extend the options again, as the dataOrOptions acts as an alias for options when a removal action is
                // specified
                options = $.extend(options, dataOrOptions);

                // If the options parameter is not the 'same' and a template string exists, the
                // temporarily override
                if (isTemplate && options.remove_type !== Remove.SAME) {

                    options.remove_type = Remove.SAME;

                } else {

                    // Otherwise use 'all'
                    options.remove_type = Remove.ALL;

                }

                // If the template is not defined, then set as null before removing
                if (!isTemplate) {

                    template = null;

                }

                return removeTemplate(this, $this, template, options);

            }

            // Assume it's an addition i.e. 'add'

            // The data object literal must contain data
            if (typeof options.validate === 'boolean' && options.validate && $.isEmptyObject(dataOrOptions)) {

                console.log('jQuery-handlebars: Data was not passed to the plugin or is simply an empty plain object');
                return this;

            }

            // jQuery object reference
            var $selector = null;

            // If a valid string template is passed then check if it's compiled or get the jQuery object instead
            if (isTemplate) {

                // If compiled already then no need to re-compile
                if (typeof compiled[template] === 'function') {

                    // Debugging only
                    console.log('jQuery-handlebars: %s has already been compiled', template);

                    // Return to continue chaining
                    return setTemplate(this, $this, template, dataOrOptions, options);

                }

                // Not compiled

                // Get the first selection only
                $selector = $(template).first();

                // If the selector doesn't exist for whatever reason, then set to null
                if ($selector.length === 0) {

                    $selector = null;

                }

                // If a valid jQuery selector object
            } else if (template instanceof $ && typeof template.length === 'number' && template.length > 0) {

                // Get the first selection only
                $selector = template.first();

                // Get the selector name for using with the compiled object literal
                // If the selector property doesn't exist, then this will be set to null
                template = typeof $selector.selector !== 'undefined' ? $selector.selector : null;

                // If compiled already then no need to re-compile
                if (template !== null && typeof compiled[template] === 'function') {

                    // Debugging only
                    console.log('jQuery-handlebars: %s has already been compiled', template);

                    // Return to continue chaining
                    return setTemplate(this, $this, template, dataOrOptions, options);

                }
            }

            // If an invalid selector, then return this to continue chaining
            if ($selector === null || template === null || $selector.length === 0) {

                // Debugging only
                console.log('jQuery-handlebars: Template cannot be found');
                return this;

            }

            // Get the HTML of the template only
            var html = $selector.html();

            // Store the compiled template in the compiled store
            compiled[template] = Handlebars.compile(html);

            // Return to continue chaining
            return setTemplate(this, $this, template, dataOrOptions, options);
        }

    });

    // Fields (Private)

    // Store the compiled template(s) using the template string as the identifier i.e. key
    var compiled = {};

    // Methods (Private)

    // Note: Variable are called 'self' to avoid conflict with 'this'

    // Get the template in the content selector
    // include refers to whether to include the template in the selection
    var getTemplate = function ($self, template, include) {

            // Get the divs with the template data attribute and optional specified template string e.g. #some-template
            var templateLocate = (include && template ? '="' + template + '"' : '');
            return $self.find('div[' + DATA_ATTRIBUTE + templateLocate + ']');

        },

        // Remove the specified template from the content selector. If a template is not provided
        // then all template(s) that are contained within the content selector will be removed
        removeTemplate = function (self, $self, template, options) {

            // If the option has been passed to remove 'none', then respect this choice or if the template is null but the option is set to 'same'
            if (options.remove_type === Remove.NONE || (template === null && options.remove_type === Remove.SAME)) {

                // Debugging only
                console.log('jQuery-handlebars: Template(s) were not removed [%s]', options.remove_type);

                // Return self to maintain chaining if there is nothing to remove
                return self;

            }

            // The include parameter will be set to true, when specifically looking for the provided template string
            var filtered = getTemplate($self, template, options.remove_type === Remove.SAME);
            if (filtered.length === 0) {

                // Debugging only
                console.log('jQuery-handlebars: Unable to find any template(s) for removal [%s]', options.remove_type);

                // Return self to maintain chaining if there is nothing to remove
                return self;

            }

            // Remove from the DOM
            filtered.remove();

            // Debugging only
            console.log('jQuery-handlebars: Removing template(s) [%s]', options.remove_type);

            // Remove the template from the compiled store, if the option is true
            if (typeof options.delete_compiled === 'boolean' && options.delete_compiled) {

                // Iterate through the filtered collection and remove from the template string from the compiled store
                $.each(filtered, function (index, element) {

                    // Get the data attribute for the template string if it's not null or has already been removed
                    var attribute = $(this).attr(DATA_ATTRIBUTE);
                    if (attribute === null || compiled[attribute] === undefined) {

                        return;

                    }

                    // Set to undefined to mimic deletion of the template
                    compiled[attribute] = undefined;

                    // Debugging only
                    console.log('jQuery-handlebars: Removing compiled template [%s]', attribute);

                });

                if (template !== null) {

                    // Debugging only
                    console.log('jQuery-handlebars: Removed the template from the compiled store [%s]', template);

                }

            }

        },

        // Set the specified template to the content selector
        setTemplate = function (self, $self, template, data, options) {

            // Previous implementations
            /*
                $self.children('*').not('script[type="text/x-handlebars-template"]').empty();
                $self.children('*:not(script[type="text/x-handlebars-template"])').empty();

                // Get all nodes apart from the Handlebarjs template script elements
                 var filtered = $self.contents().filter(function () {

                    return this.nodeType !== Node.COMMENT_NODE && // Not a comment
                        (this.nodeName !== 'SCRIPT' && this.type !== 'text/x-handlebars-template') // Not a handlebars template
                        ; // /[^\t\n\r ]/.test(this.textContent); // Not whitespace

                });
             */

            // The include parameter will be set to true, when specifically looking for the provided template string
            var filtered = getTemplate($self, template, options.remove_type === Remove.SAME);

            // Remove from the DOM if specified to do so
            if (options.remove_type !== Remove.NONE) {

                // Remove from the DOM
                filtered.remove();

                // Debugging only
                console.log('jQuery-handlebars: Removed previous template(s) [%s]', options.remove_type);

            }

            // If set to not refill and template node(s) exist, then return this
            if (typeof options.refill === 'boolean' && !options.refill) {

                // Get the template(s) after potential removal. The parameter include has been set to true, as we are checking
                // if only the same template(s) exists (perhaps an option could/should be created)
                filtered = getTemplate($self, template, true);
                if (filtered.length > 0) {

                    // Debugging only
                    console.log('jQuery-handlebars: Refill has been set to false and the content element contains template(s) [%s]', options.remove_type);

                    return self;

                }

            }

            // Append to the content element by checking the type. Default is 'append'
            switch (options.type) {
                case Type.COMPILED:
                case Type.RAW:

                    // Debugging only
                    console.log('jQuery-handlebars: Returning raw HTML', template);

                    // Return the compiled HTML
                    return compiled[template](data);

                default:

                    // Create a div element with the template appended to it
                    // This contains a data-* attribute called data-jquery-handlebars for easy association
                    // that it's a Handlebarjs template
                    var $div = $('<div/>')
                        .attr(DATA_ATTRIBUTE, template)
                        .append(compiled[template](data));

                    // Append the div to content element
                    $self.append($div);

                    // Debugging only
                    console.log('jQuery-handlebars: Appending template to the content element [%s]', template);
                    break;

            }

            return self;
        };

    // Constants

    // The data-* attribute name
    var DATA_ATTRIBUTE = 'data-jquery-handlebars',

        // Removal constants. Who enjoys magic values?
        Remove = {
            ALL: 'ALL',
            NONE: 'NONE',
            SAME: 'SAME'
        },

        // Type constants. Who enjoys magic values?
        Type = {
            APPEND: 'APPEND',
            COMPILED: 'COMPILED',
            RAW: 'RAW'
        };

    // Defaults

    $.fn.handlebars.options = {
        // Delete the template from the compiled store on removal
        delete_compiled: true,

        // Allow the option of adding multiple template(s) inside an element
        refill: true,

        // Remove pre-existing compiled templates from the content element
        //
        // The following options are:
        //      'all': Remove all valid template(s) from the content element
        //      'none' (default): Don't remove any template(s)
        //      'same': Remove only those template(s) that match the provided template string
        remove_type: Remove.NONE,

        // How to output the compiled template to the content element
        //
        // The following options are:
        //      'append' (default): Append to the content element
        //      'compiled': Return a compiled template as HTML
        //      'raw': Return a compiled template as HTML
        type: Type.APPEND,

        // Check whether the data passed to the plugin is empty
        validate: true
    };

})(jQuery);
<commit_msg>Commented out console lines<commit_after>/* global Handlebars */
/*
 * jQuery-handlebars
 * https://github.com/softwarespot/jquery-handlebars
 * Author: softwarespot
 * Licensed under the MIT license
 * Version: 0.1.0-beta
 */
; (function ($, undefined) {

    // Initial idea from: http://blog.teamtreehouse.com/handlebars-js-part-3-tips-and-tricks

    // Plugin Logic
    $.fn.extend({

        // data doubles up as options when the action is either 'clear' or 'remove'
        handlebars: function (action, template, dataOrOptions, options) {

            // jQuery object reference for this. Only select the first selector of the collection
            var $this = $(this).first(),

                // The following was taken from sizzle.js, URL: https://github.com/jquery/sizzle/blob/master/dist/sizzle.js

                // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
                identifier = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+',

                rIdentifier = new RegExp('^#' + identifier + '$');

                // Store whether the template is valid or not
                isTemplate = typeof template === 'string' && rIdentifier.test(template);

            // Debugging only
            // console.log('jQuery-handlebars: Is a template? [%s, %s]', template, isTemplate);

            // These actions don't require any pre-processing
            if (typeof action === 'string') {

                // If a 'get' action is provided, then get the template(s)
                // The template string if not defined will return all template(s)
                if (/^FIND|GET$/i.test(action)) {

                    // The include parameter will be true, if the template is a string and valid anchor
                    return getTemplate($this, template, isTemplate);

                // If a 'compiled' action is provided, then return the compiled object literal
                } else if (/^COMPILED$/i.test(action)) {

                    return compiled;

                }

            }

            // Set our options from the defaults, overriding with the
            // parameter we pass into this function
            options = $.extend({}, $.fn.handlebars.options, options);

            // START: Sanitize the options

            // Check if the option type is a string and valid
            if (typeof options.type === 'string' && /^APPEND|COMPILED|RAW$/i.test(options.type)) {

                // Set to uppercase
                options.type = options.type.toUpperCase();

            } else {

                // Otherwise default to 'append' i.e. if null or simply invalid
                options.type = Type.APPEND;

            }

            // If the removal type is a string and valid, then set to uppercase
            if (typeof options.remove_type === 'string' && /^ALL|NONE|SAME$/i.test(options.remove_type)) {

                // Set to uppercase
                options.remove_type = options.remove_type.toUpperCase();

            } else {

                // Otherwise default to 'none' i.e. if null or simply is invalid
                options.remove_type = Remove.NONE;

            }

            // END: Sanitize the options

            // If a 'clear' action is provided, then remove the template from the content element
            if (/^CLEAR|REMOVE$/i.test(action)) {

                // Extend the options again, as the dataOrOptions acts as an alias for options when a removal action is
                // specified
                options = $.extend(options, dataOrOptions);

                // If the options parameter is not the 'same' and a template string exists, the
                // temporarily override
                if (isTemplate && options.remove_type !== Remove.SAME) {

                    options.remove_type = Remove.SAME;

                } else {

                    // Otherwise use 'all'
                    options.remove_type = Remove.ALL;

                }

                // If the template is not defined, then set as null before removing
                if (!isTemplate) {

                    template = null;

                }

                return removeTemplate(this, $this, template, options);

            }

            // Assume it's an addition i.e. 'add'

            // The data object literal must contain data
            if (typeof options.validate === 'boolean' && options.validate && $.isEmptyObject(dataOrOptions)) {

                // console.log('jQuery-handlebars: Data was not passed to the plugin or is simply an empty plain object');
                return this;

            }

            // jQuery object reference
            var $selector = null;

            // If a valid string template is passed then check if it's compiled or get the jQuery object instead
            if (isTemplate) {

                // If compiled already then no need to re-compile
                if (typeof compiled[template] === 'function') {

                    // Debugging only
                    // console.log('jQuery-handlebars: %s has already been compiled', template);

                    // Return to continue chaining
                    return setTemplate(this, $this, template, dataOrOptions, options);

                }

                // Not compiled

                // Get the first selection only
                $selector = $(template).first();

                // If the selector doesn't exist for whatever reason, then set to null
                if ($selector.length === 0) {

                    $selector = null;

                }

                // If a valid jQuery selector object
            } else if (template instanceof $ && typeof template.length === 'number' && template.length > 0) {

                // Get the first selection only
                $selector = template.first();

                // Get the selector name for using with the compiled object literal
                // If the selector property doesn't exist, then this will be set to null
                template = typeof $selector.selector !== 'undefined' ? $selector.selector : null;

                // If compiled already then no need to re-compile
                if (template !== null && typeof compiled[template] === 'function') {

                    // Debugging only
                    // console.log('jQuery-handlebars: %s has already been compiled', template);

                    // Return to continue chaining
                    return setTemplate(this, $this, template, dataOrOptions, options);

                }
            }

            // If an invalid selector, then return this to continue chaining
            if ($selector === null || template === null || $selector.length === 0) {

                // Debugging only
                // console.log('jQuery-handlebars: Template cannot be found');
                return this;

            }

            // Get the HTML of the template only
            var html = $selector.html();

            // Store the compiled template in the compiled store
            compiled[template] = Handlebars.compile(html);

            // Return to continue chaining
            return setTemplate(this, $this, template, dataOrOptions, options);
        }

    });

    // Fields (Private)

    // Store the compiled template(s) using the template string as the identifier i.e. key
    var compiled = {};

    // Methods (Private)

    // Note: Variable are called 'self' to avoid conflict with 'this'

    // Get the template in the content selector
    // include refers to whether to include the template in the selection
    var getTemplate = function ($self, template, include) {

            // Get the divs with the template data attribute and optional specified template string e.g. #some-template
            var templateLocate = (include && template ? '="' + template + '"' : '');
            return $self.find('div[' + DATA_ATTRIBUTE + templateLocate + ']');

        },

        // Remove the specified template from the content selector. If a template is not provided
        // then all template(s) that are contained within the content selector will be removed
        removeTemplate = function (self, $self, template, options) {

            // If the option has been passed to remove 'none', then respect this choice or if the template is null but the option is set to 'same'
            if (options.remove_type === Remove.NONE || (template === null && options.remove_type === Remove.SAME)) {

                // Debugging only
                // console.log('jQuery-handlebars: Template(s) were not removed [%s]', options.remove_type);

                // Return self to maintain chaining if there is nothing to remove
                return self;

            }

            // The include parameter will be set to true, when specifically looking for the provided template string
            var filtered = getTemplate($self, template, options.remove_type === Remove.SAME);
            if (filtered.length === 0) {

                // Debugging only
                // console.log('jQuery-handlebars: Unable to find any template(s) for removal [%s]', options.remove_type);

                // Return self to maintain chaining if there is nothing to remove
                return self;

            }

            // Remove from the DOM
            filtered.remove();

            // Debugging only
            // console.log('jQuery-handlebars: Removing template(s) [%s]', options.remove_type);

            // Remove the template from the compiled store, if the option is true
            if (typeof options.delete_compiled === 'boolean' && options.delete_compiled) {

                // Iterate through the filtered collection and remove from the template string from the compiled store
                $.each(filtered, function (index, element) {

                    // Get the data attribute for the template string if it's not null or has already been removed
                    var attribute = $(this).attr(DATA_ATTRIBUTE);
                    if (attribute === null || compiled[attribute] === undefined) {

                        return;

                    }

                    // Set to undefined to mimic deletion of the template
                    compiled[attribute] = undefined;

                    // Debugging only
                    // console.log('jQuery-handlebars: Removing compiled template [%s]', attribute);

                });

                if (template !== null) {

                    // Debugging only
                    // console.log('jQuery-handlebars: Removed the template from the compiled store [%s]', template);

                }

            }

        },

        // Set the specified template to the content selector
        setTemplate = function (self, $self, template, data, options) {

            // Previous implementations
            /*
                $self.children('*').not('script[type="text/x-handlebars-template"]').empty();
                $self.children('*:not(script[type="text/x-handlebars-template"])').empty();

                // Get all nodes apart from the Handlebarjs template script elements
                 var filtered = $self.contents().filter(function () {

                    return this.nodeType !== Node.COMMENT_NODE && // Not a comment
                        (this.nodeName !== 'SCRIPT' && this.type !== 'text/x-handlebars-template') // Not a handlebars template
                        ; // /[^\t\n\r ]/.test(this.textContent); // Not whitespace

                });
             */

            // The include parameter will be set to true, when specifically looking for the provided template string
            var filtered = getTemplate($self, template, options.remove_type === Remove.SAME);

            // Remove from the DOM if specified to do so
            if (options.remove_type !== Remove.NONE) {

                // Remove from the DOM
                filtered.remove();

                // Debugging only
                // console.log('jQuery-handlebars: Removed previous template(s) [%s]', options.remove_type);

            }

            // If set to not refill and template node(s) exist, then return this
            if (typeof options.refill === 'boolean' && !options.refill) {

                // Get the template(s) after potential removal. The parameter include has been set to true, as we are checking
                // if only the same template(s) exists (perhaps an option could/should be created)
                filtered = getTemplate($self, template, true);
                if (filtered.length > 0) {

                    // Debugging only
                    // console.log('jQuery-handlebars: Refill has been set to false and the content element contains template(s) [%s]', options.remove_type);

                    return self;

                }

            }

            // Append to the content element by checking the type. Default is 'append'
            switch (options.type) {
                case Type.COMPILED:
                case Type.RAW:

                    // Debugging only
                    // console.log('jQuery-handlebars: Returning raw HTML', template);

                    // Return the compiled HTML
                    return compiled[template](data);

                default:

                    // Create a div element with the template appended to it
                    // This contains a data-* attribute called data-jquery-handlebars for easy association
                    // that it's a Handlebarjs template
                    var $div = $('<div/>')
                        .attr(DATA_ATTRIBUTE, template)
                        .append(compiled[template](data));

                    // Append the div to content element
                    $self.append($div);

                    // Debugging only
                    // console.log('jQuery-handlebars: Appending template to the content element [%s]', template);
                    break;

            }

            return self;
        };

    // Constants

    // The data-* attribute name
    var DATA_ATTRIBUTE = 'data-jquery-handlebars',

        // Removal constants. Who enjoys magic values?
        Remove = {
            ALL: 'ALL',
            NONE: 'NONE',
            SAME: 'SAME'
        },

        // Type constants. Who enjoys magic values?
        Type = {
            APPEND: 'APPEND',
            COMPILED: 'COMPILED',
            RAW: 'RAW'
        };

    // Defaults

    $.fn.handlebars.options = {
        // Delete the template from the compiled store on removal
        delete_compiled: true,

        // Allow the option of adding multiple template(s) inside an element
        refill: true,

        // Remove pre-existing compiled templates from the content element
        //
        // The following options are:
        //      'all': Remove all valid template(s) from the content element
        //      'none' (default): Don't remove any template(s)
        //      'same': Remove only those template(s) that match the provided template string
        remove_type: Remove.NONE,

        // How to output the compiled template to the content element
        //
        // The following options are:
        //      'append' (default): Append to the content element
        //      'compiled': Return a compiled template as HTML
        //      'raw': Return a compiled template as HTML
        type: Type.APPEND,

        // Check whether the data passed to the plugin is empty
        validate: true
    };

})(jQuery);
<|endoftext|>