<commit_before>// The Regexper class manages the top-level behavior for the entire
// application. This includes event handlers for all user interactions.

import util from './util.js';
import Parser from './parser/javascript.js';
import _ from 'lodash';

export default class Regexper {
  constructor(root) {
    this.root = root;
    this.buggyHash = false;
    this.form = root.querySelector('#regexp-form');
    this.field = root.querySelector('#regexp-input');
    this.error = root.querySelector('#error');
    this.warnings = root.querySelector('#warnings');

    this.links = this.form.querySelector('ul');
    this.permalink = this.links.querySelector('a[data-action="permalink"]');
    this.downloadSvg = this.links.querySelector('a[data-action="download-svg"]');
    this.downloadPng = this.links.querySelector('a[data-action="download-png"]');

    this.svgContainer = root.querySelector('#regexp-render');
  }

  // Event handler for key presses in the regular expression form field.
  keypressListener(event) {
    // Pressing Shift-Enter displays the expression.
    if (event.shiftKey && event.keyCode === 13) {
      event.returnValue = false;
      if (event.preventDefault) {
        event.preventDefault();
      }

      this.form.dispatchEvent(util.customEvent('submit'));
    }
  }

  // Event handler for key presses while focused anywhere in the application.
  documentKeypressListener(event) {
    // Pressing escape will cancel a currently running render.
    if (event.keyCode === 27 && this.running) {
      this.running.cancel();
    }
  }

  // Event handler for submission of the regular expression. Changes the URL
  // hash which leads to the expression being rendered.
  submitListener(event) {
    event.returnValue = false;
    if (event.preventDefault) {
      event.preventDefault();
    }

    try {
      this._setHash(this.field.value);
    }
    catch(e) {
      // Failed to set the URL hash (probably because the expression is too
      // long). Turn off display of the permalink and just show the expression.
      this.permalinkEnabled = false;
      this.showExpression(this.field.value);
    }
  }

  // Event handler for URL hash changes. Starts rendering of the expression.
  hashchangeListener() {
    let expr = this._getHash();

    if (expr instanceof Error) {
      this.state = 'has-error';
      this.error.innerHTML = 'Malformed expression in URL';
      util.track('send', 'event', 'visualization', 'malformed URL');
    } else {
      this.permalinkEnabled = true;
      this.showExpression(expr);
    }
  }

  // Binds all event listeners.
  bindListeners() {
    this.field.addEventListener('keypress', this.keypressListener.bind(this));
    this.form.addEventListener('submit', this.submitListener.bind(this));
    this.root.addEventListener('keyup', this.documentKeypressListener.bind(this));
    window.addEventListener('hashchange', this.hashchangeListener.bind(this));
  }

  // Detect if https://bugzilla.mozilla.org/show_bug.cgi?id=483304 is in effect
  detectBuggyHash() {
    if (typeof window.URL === 'function') {
      try {
        let url = new URL('http://regexper.com/#%25');
        this.buggyHash = (url.hash === '#%');
      }
      catch(e) {
        this.buggyHash = false;
      }
    }
  }

  // Set the URL hash. This method exists to facilitate automated testing
  // (since changing the URL can throw off most JavaScript testing tools).
  _setHash(hash) {
    location.hash = encodeURIComponent(hash)
      .replace(/\(/g, '%28')
      .replace(/\)/g, '%29');
  }

  // Retrieve the current URL hash. This method is also mostly for supporting
  // automated testing, but also does some basic error handling for malformed
  // URLs.
  _getHash() {
    try {
      let hash = location.hash.slice(1)
      return this.buggyHash ? hash : decodeURIComponent(hash);
    }
    catch(e) {
      return e;
    }
  }

  // Currently state of the application. Useful values are:
  //  - `''` - State of the application when the page initially loads
  //  - `'is-loading'` - Displays the loading indicator
  //  - `'has-error'` - Displays the error message
  //  - `'has-results'` - Displays rendered results
  set state(state) {
    this.root.className = state;
  }

  get state() {
    return this.root.className;
  }

  // Start the rendering of a regular expression.
  //
  // - __expression__ - Regular expression to display.
  showExpression(expression) {
    this.field.value = expression;
    this.state = '';

    if (expression !== '') {
      this.renderRegexp(expression).catch(util.exposeError);
    }
  }

  // Creates a blob URL for linking to a rendered regular expression image.
  //
  // - __content__ - SVG image markup.
  buildBlobURL(content) {
    // Blob object has to stick around for IE, so the instance is stored on the
    // `window` object.
    window.blob = new Blob([content], { type: 'image/svg+xml' });
    return URL.createObjectURL(window.blob);
  }

  // Update the URLs of the 'download' and 'permalink' links.
  updateLinks() {
    let classes = _.without(this.links.className.split(' '), ['hide-download-svg', 'hide-permalink']);
    let svg = this.svgContainer.querySelector('.svg');

    // Create the SVG 'download' image URL.
    try {
      this.downloadSvg.parentNode.style.display = null;
      this.downloadSvg.href = this.buildBlobURL(svg.innerHTML);
    }
    catch(e) {
      // Blobs or URLs created from a blob URL don't work in the current
      // browser. Giving up on the download link.
      classes.push('hide-download-svg');
    }

    //Create the PNG 'download' image URL.
    try {
      let canvas = document.createElement('canvas');
      let context = canvas.getContext('2d');
      let loader = new Image;

      loader.width = canvas.width = Number(svg.querySelector('svg').getAttribute('width'));
      loader.height = canvas.height = Number(svg.querySelector('svg').getAttribute('height'));
      loader.onload = () => {
        try {
          context.drawImage(loader, 0, 0, loader.width, loader.height);
          canvas.toBlob(blob => {
            window.pngBlob = blob;
            this.downloadPng.href = URL.createObjectURL(window.pngBlob);
            this.links.className = this.links.className.replace(/\bhide-download-png\b/, '');
          }, 'image/png');
        }
        catch(e) {}
      };
      loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg.innerHTML);
      classes.push('hide-download-png');
    }
    catch(e) {}

    // Create the 'permalink' URL.
    if (this.permalinkEnabled) {
      this.permalink.parentNode.style.display = null;
      this.permalink.href = location.toString();
    } else {
      classes.push('hide-permalink');
    }

    this.links.className = classes.join(' ');
  }

  // Display any warnings that were generated while rendering a regular expression.
  //
  // - __warnings__ - Array of warning messages to display.
  displayWarnings(warnings) {
    this.warnings.innerHTML = _.map(warnings, warning => (
      `<li class="inline-icon">${util.icon("#warning")}${warning}</li>`
    )).join('');
  }

  // Render regular expression
  //
  // - __expression__ - Regular expression to render
  renderRegexp(expression) {
    let parseError = false,
        startTime, endTime;

    // When a render is already in progress, cancel it and try rendering again
    // after a short delay (canceling a render is not instantaneous).
    if (this.running) {
      this.running.cancel();

      return util.wait(10).then(() => this.renderRegexp(expression));
    }

    this.state = 'is-loading';
    util.track('send', 'event', 'visualization', 'start');
    startTime = new Date().getTime();

    this.running = new Parser(this.svgContainer);

    return this.running
      // Parse the expression.
      .parse(expression)
      // Display any error messages from the parser and abort the render.
      .catch(message => {
        this.state = 'has-error';
        this.error.innerHTML = '';
        this.error.appendChild(document.createTextNode(message));

        parseError = true;

        throw message;
      })
      // When parsing is successful, render the parsed expression.
      .then(parser => parser.render())
      // Once rendering is complete:
      //  - Update links
      //  - Display any warnings
      //  - Track the completion of the render and how long it took
      .then(() => {
        this.state = 'has-results';
        this.updateLinks();
        this.displayWarnings(this.running.warnings);
        util.track('send', 'event', 'visualization', 'complete');

        endTime = new Date().getTime();
        util.track('send', 'timing', 'visualization', 'total time', endTime - startTime);
      })
      // Handle any errors that happened during the rendering pipeline.
      // Swallows parse errors and render cancellations. Any other exceptions
      // are allowed to continue on to be tracked by the global error handler.
      .catch(message => {
        if (message === 'Render cancelled') {
          util.track('send', 'event', 'visualization', 'cancelled');
          this.state = '';
        } else if (parseError) {
          util.track('send', 'event', 'visualization', 'parse error');
        } else {
          throw message;
        }
      })
      // Finally, mark rendering as complete (and pass along any exceptions
      // that were thrown).
      .then(
        () => {
          this.running = false;
        },
        message => {
          this.running = false;
          throw message;
        }
      );
  }
}
<commit_msg>Ignoring errors when building blob URL for PNG<commit_after>// The Regexper class manages the top-level behavior for the entire
// application. This includes event handlers for all user interactions.

import util from './util.js';
import Parser from './parser/javascript.js';
import _ from 'lodash';

export default class Regexper {
  constructor(root) {
    this.root = root;
    this.buggyHash = false;
    this.form = root.querySelector('#regexp-form');
    this.field = root.querySelector('#regexp-input');
    this.error = root.querySelector('#error');
    this.warnings = root.querySelector('#warnings');

    this.links = this.form.querySelector('ul');
    this.permalink = this.links.querySelector('a[data-action="permalink"]');
    this.downloadSvg = this.links.querySelector('a[data-action="download-svg"]');
    this.downloadPng = this.links.querySelector('a[data-action="download-png"]');

    this.svgContainer = root.querySelector('#regexp-render');
  }

  // Event handler for key presses in the regular expression form field.
  keypressListener(event) {
    // Pressing Shift-Enter displays the expression.
    if (event.shiftKey && event.keyCode === 13) {
      event.returnValue = false;
      if (event.preventDefault) {
        event.preventDefault();
      }

      this.form.dispatchEvent(util.customEvent('submit'));
    }
  }

  // Event handler for key presses while focused anywhere in the application.
  documentKeypressListener(event) {
    // Pressing escape will cancel a currently running render.
    if (event.keyCode === 27 && this.running) {
      this.running.cancel();
    }
  }

  // Event handler for submission of the regular expression. Changes the URL
  // hash which leads to the expression being rendered.
  submitListener(event) {
    event.returnValue = false;
    if (event.preventDefault) {
      event.preventDefault();
    }

    try {
      this._setHash(this.field.value);
    }
    catch(e) {
      // Failed to set the URL hash (probably because the expression is too
      // long). Turn off display of the permalink and just show the expression.
      this.permalinkEnabled = false;
      this.showExpression(this.field.value);
    }
  }

  // Event handler for URL hash changes. Starts rendering of the expression.
  hashchangeListener() {
    let expr = this._getHash();

    if (expr instanceof Error) {
      this.state = 'has-error';
      this.error.innerHTML = 'Malformed expression in URL';
      util.track('send', 'event', 'visualization', 'malformed URL');
    } else {
      this.permalinkEnabled = true;
      this.showExpression(expr);
    }
  }

  // Binds all event listeners.
  bindListeners() {
    this.field.addEventListener('keypress', this.keypressListener.bind(this));
    this.form.addEventListener('submit', this.submitListener.bind(this));
    this.root.addEventListener('keyup', this.documentKeypressListener.bind(this));
    window.addEventListener('hashchange', this.hashchangeListener.bind(this));
  }

  // Detect if https://bugzilla.mozilla.org/show_bug.cgi?id=483304 is in effect
  detectBuggyHash() {
    if (typeof window.URL === 'function') {
      try {
        let url = new URL('http://regexper.com/#%25');
        this.buggyHash = (url.hash === '#%');
      }
      catch(e) {
        this.buggyHash = false;
      }
    }
  }

  // Set the URL hash. This method exists to facilitate automated testing
  // (since changing the URL can throw off most JavaScript testing tools).
  _setHash(hash) {
    location.hash = encodeURIComponent(hash)
      .replace(/\(/g, '%28')
      .replace(/\)/g, '%29');
  }

  // Retrieve the current URL hash. This method is also mostly for supporting
  // automated testing, but also does some basic error handling for malformed
  // URLs.
  _getHash() {
    try {
      let hash = location.hash.slice(1)
      return this.buggyHash ? hash : decodeURIComponent(hash);
    }
    catch(e) {
      return e;
    }
  }

  // Currently state of the application. Useful values are:
  //  - `''` - State of the application when the page initially loads
  //  - `'is-loading'` - Displays the loading indicator
  //  - `'has-error'` - Displays the error message
  //  - `'has-results'` - Displays rendered results
  set state(state) {
    this.root.className = state;
  }

  get state() {
    return this.root.className;
  }

  // Start the rendering of a regular expression.
  //
  // - __expression__ - Regular expression to display.
  showExpression(expression) {
    this.field.value = expression;
    this.state = '';

    if (expression !== '') {
      this.renderRegexp(expression).catch(util.exposeError);
    }
  }

  // Creates a blob URL for linking to a rendered regular expression image.
  //
  // - __content__ - SVG image markup.
  buildBlobURL(content) {
    // Blob object has to stick around for IE, so the instance is stored on the
    // `window` object.
    window.blob = new Blob([content], { type: 'image/svg+xml' });
    return URL.createObjectURL(window.blob);
  }

  // Update the URLs of the 'download' and 'permalink' links.
  updateLinks() {
    let classes = _.without(this.links.className.split(' '), ['hide-download-svg', 'hide-permalink']);
    let svg = this.svgContainer.querySelector('.svg');

    // Create the SVG 'download' image URL.
    try {
      this.downloadSvg.parentNode.style.display = null;
      this.downloadSvg.href = this.buildBlobURL(svg.innerHTML);
    }
    catch(e) {
      // Blobs or URLs created from a blob URL don't work in the current
      // browser. Giving up on the download link.
      classes.push('hide-download-svg');
    }

    //Create the PNG 'download' image URL.
    try {
      let canvas = document.createElement('canvas');
      let context = canvas.getContext('2d');
      let loader = new Image;

      loader.width = canvas.width = Number(svg.querySelector('svg').getAttribute('width'));
      loader.height = canvas.height = Number(svg.querySelector('svg').getAttribute('height'));
      loader.onload = () => {
        try {
          context.drawImage(loader, 0, 0, loader.width, loader.height);
          canvas.toBlob(blob => {
            try {
              window.pngBlob = blob;
              this.downloadPng.href = URL.createObjectURL(window.pngBlob);
              this.links.className = this.links.className.replace(/\bhide-download-png\b/, '');
            }
            catch(e) {}
          }, 'image/png');
        }
        catch(e) {}
      };
      loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg.innerHTML);
      classes.push('hide-download-png');
    }
    catch(e) {}

    // Create the 'permalink' URL.
    if (this.permalinkEnabled) {
      this.permalink.parentNode.style.display = null;
      this.permalink.href = location.toString();
    } else {
      classes.push('hide-permalink');
    }

    this.links.className = classes.join(' ');
  }

  // Display any warnings that were generated while rendering a regular expression.
  //
  // - __warnings__ - Array of warning messages to display.
  displayWarnings(warnings) {
    this.warnings.innerHTML = _.map(warnings, warning => (
      `<li class="inline-icon">${util.icon("#warning")}${warning}</li>`
    )).join('');
  }

  // Render regular expression
  //
  // - __expression__ - Regular expression to render
  renderRegexp(expression) {
    let parseError = false,
        startTime, endTime;

    // When a render is already in progress, cancel it and try rendering again
    // after a short delay (canceling a render is not instantaneous).
    if (this.running) {
      this.running.cancel();

      return util.wait(10).then(() => this.renderRegexp(expression));
    }

    this.state = 'is-loading';
    util.track('send', 'event', 'visualization', 'start');
    startTime = new Date().getTime();

    this.running = new Parser(this.svgContainer);

    return this.running
      // Parse the expression.
      .parse(expression)
      // Display any error messages from the parser and abort the render.
      .catch(message => {
        this.state = 'has-error';
        this.error.innerHTML = '';
        this.error.appendChild(document.createTextNode(message));

        parseError = true;

        throw message;
      })
      // When parsing is successful, render the parsed expression.
      .then(parser => parser.render())
      // Once rendering is complete:
      //  - Update links
      //  - Display any warnings
      //  - Track the completion of the render and how long it took
      .then(() => {
        this.state = 'has-results';
        this.updateLinks();
        this.displayWarnings(this.running.warnings);
        util.track('send', 'event', 'visualization', 'complete');

        endTime = new Date().getTime();
        util.track('send', 'timing', 'visualization', 'total time', endTime - startTime);
      })
      // Handle any errors that happened during the rendering pipeline.
      // Swallows parse errors and render cancellations. Any other exceptions
      // are allowed to continue on to be tracked by the global error handler.
      .catch(message => {
        if (message === 'Render cancelled') {
          util.track('send', 'event', 'visualization', 'cancelled');
          this.state = '';
        } else if (parseError) {
          util.track('send', 'event', 'visualization', 'parse error');
        } else {
          throw message;
        }
      })
      // Finally, mark rendering as complete (and pass along any exceptions
      // that were thrown).
      .then(
        () => {
          this.running = false;
        },
        message => {
          this.running = false;
          throw message;
        }
      );
  }
}
<|endoftext|>


<commit_before>import _ from 'lodash'

export function transformString (uis1Transform) {
  const uis2Transform = {
    from: _.keys(uis1Transform)[0],
    to: uis1Transform[_.keys(uis1Transform)[0]],
    global: true
  }
  return uis2Transform
}

export function transformObject (uis1Transform) {
  const keywords = ['value', 'id', 'label']
  return {
    object: _.reduce(uis1Transform, (result, value, key) => {
      if (value.split('\'').length === 3) {
        value = value.split('\'')[1]
      }
      if (_.indexOf(keywords, value) !== -1) {
        value = '${' + value + '}'
      }
      result[key] = value
      return result
    }, {})
  }
}

export function setTransforms (newField, oldField, logger) {
  const transforms = {}
  if (oldField.modelTransform) {
    if (oldField.modelTransform['toObject']) {
      transforms.write = [transformObject(oldField.modelTransform.toObject)]
    }
    if (oldField.modelTransform['replace']) {
      transforms.write = [transformString(oldField.modelTransform.replace)]
    }
  }
  if (oldField.viewTransform) {
    if (oldField.viewTransform['toObject']) {
      transforms.read = [transformObject(oldField.viewTransform.toObject)]
    }
    if (oldField.viewTransform['replace']) {
      transforms.read = [transformString(oldField.viewTransform.replace)]
    }
  }
  if (!_.isEmpty(transforms)) _.set(newField, 'transforms', transforms)
  return newField
}
<commit_msg>PR issue addressed<commit_after>import _ from 'lodash'

export function transformString (uis1Transform) {
  const key = _.keys(uis1Transform)[0]
  const uis2Transform = {
    from: key,
    to: uis1Transform[key],
    global: true
  }
  return uis2Transform
}

export function transformObject (uis1Transform) {
  const keywords = ['value', 'id', 'label']
  return {
    object: _.reduce(uis1Transform, (result, value, key) => {
      if (value.split('\'').length === 3) {
        value = value.split('\'')[1]
      }
      if (_.indexOf(keywords, value) !== -1) {
        value = '${' + value + '}'
      }
      result[key] = value
      return result
    }, {})
  }
}

export function setTransforms (newField, oldField, logger) {
  const transforms = {}
  if (oldField.modelTransform) {
    if (oldField.modelTransform['toObject']) {
      transforms.write = [transformObject(oldField.modelTransform.toObject)]
    }
    if (oldField.modelTransform['replace']) {
      transforms.write = [transformString(oldField.modelTransform.replace)]
    }
  }
  if (oldField.viewTransform) {
    if (oldField.viewTransform['toObject']) {
      transforms.read = [transformObject(oldField.viewTransform.toObject)]
    }
    if (oldField.viewTransform['replace']) {
      transforms.read = [transformString(oldField.viewTransform.replace)]
    }
  }
  if (!_.isEmpty(transforms)) _.set(newField, 'transforms', transforms)
  return newField
}
<|endoftext|>


<commit_before>function retrieveGetParameters() {
    let parameters = {}
    window.location.search
        .substring(1)                                       //Remove '?' at beginning
        .split('&')                                         //Split key-value pairs
        .map((currentElement) => {                          //Fill parameters object with key-value pairs
            const key = currentElement.split('=')[0];
            const value = currentElement.split('=')[1];
            parameters[key] = value;
        })
    return parameters;
}

function getTeamname() {
    const parameters = retrieveGetParameters();
    return parameters["teamname"];
}

function updateTeamname() {
    $('h1')
        .text(getTeamname())
    $('#teamname_input')
        .val(getTeamname());
    $('#teamname_for_selector')
        .val(getTeamname());
}

function getAllSuggestions() {
    let suggestions;
    $.ajax({
        url: '/pizzas/suggestions/?teamname=' + getTeamname(),
        success: (res) => {
            suggestions = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return suggestions
}

function vote(value, id) {
    const voteObject = {
        teamname: getTeamname(),
        id: id,
        vote: value
    };
    $.ajax({
        url: '/pizzas/suggestions/vote',
        data: voteObject,
        success: () => {
            console.log("Your vote has been sent to the server.")
            window.location.reload(true);
        },
        method: "POST"
    })
}

function voteHandler(event) {
    const storage = window.localStorage;
    const $eventTarget = event.target;
    const suggestionID = $(event.target).parent().parent().parent().parent().parent().parent().parent().attr('suggestion-id');
    if ($($eventTarget).hasClass('btn-success')
        && storage.getItem('voted_for_suggestion_id_' + suggestionID) !== 'positive') {
        //Send & save positive vote
        if (storage.getItem('voted_for_suggestion_id_' + suggestionID) === 'negative') {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'neutral');
        } else {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'positive');
        }
        vote(1, suggestionID);
    } else if ($($eventTarget).hasClass('btn-danger')
        && storage.getItem('voted_for_suggestion_id_' + suggestionID) !== 'negative') {
        //Send & save negative vote
        if (storage.getItem('voted_for_suggestion_id_' + suggestionID) === 'positive') {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'neutral');
        } else {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'negative');
        }
        vote(-1, suggestionID);
    }
}

/**
 * Generate DOM structure for a pizza topping suggestion
 * @param id suggestion ID assigned by the backend for this suggestion
 * @param headingText text to be displayed in header of the suggestion
 * @param voteCount number of votes this suggestion has already received
 * @param success {boolean} whether this topping is currently successful and will be chose
 * @param ingredients list of ingredients used as pizza topping suggestion
 * @returns {*|jQuery}
 */
function generateSuggestionPanel(id, headingText, voteCount, success, ingredients) {
    const ingredientsList = ingredients
        .concat((new Array(4 - ingredients.length)).fill("-"))
        .reduce((acc, val) => {
            return acc + val + "
"
        }, "")
    const $headingRow = $('

')
        .addClass('row');
    const $h4 = $('

')
    const $panelHeadingText = $('

')
        .addClass('col-sm-6');
    const $panelHeadingVote = $('

')
        .addClass('col-sm-6')
    const $elementGroup = $('

')
        .addClass('element-group')

                                const $posButton = $('<button></button>')
                                    .attr('type', 'button')
                                    .addClass('btn btn-success btn-xs')
                                    .text('+')
                                    .click(voteHandler)
                                const $voteCount = $('')
                                    .addClass('label label-default label-sm')
                                    .text(voteCount)
                                const $negButton = $('<button></button>')
                                    .attr('type', 'button')
                                    .addClass('btn btn-danger btn-xs')
                                    .text('-')
                                    .click(voteHandler)

                            const $voteComponent = $($elementGroup)
                                .append($posButton)
                                .append($voteCount)
                                .append($negButton)

                        const $voteColumn = $($panelHeadingVote)
                            .append($voteComponent)

                        const $headingTextColumn = $($panelHeadingText)
                            .text(headingText)

                    const $header = $($h4)
                        .append($headingTextColumn)
                        .append($voteColumn)

                const $completeHeadingRow = $($headingRow)
                    .append($header)

            const $panelHeading = $('

')
                .addClass('panel-heading')
                .append($completeHeadingRow)

            const $panelBody = $('

')
                .addClass('panel-body')
                .html(ingredientsList.slice(0, -4))

        const $panel = $('

')
            .addClass('panel ' + ((success) ? 'panel-success' : 'panel-danger'))
            .append($panelHeading)
            .append($panelBody)

    const $panelColumn = $('

')
        .addClass('col-sm-4')
        .attr('suggestion-id', id)
        .append($panel)

    return $panelColumn;
}

function getAmount() {
    let amount;
    $.ajax({
        url: '/pizzas/amount/?teamname=' + getTeamname(),
        success: (res) => {
            amount = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return (amount) ? amount.amount : 0;
}

function getTeamdata() {
    let data;
    $.ajax({
        url: '/teams/data/?teamname=' + getTeamname(),
        success: (res) => {
            data = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return data
}

window.addEventListener('load', () => {
    updateTeamname();

    // multiplied by two, because here we work with half pizzas
    amount = getAmount() * 2;

    teamdata = getTeamdata();
    document.getElementById("count").value = teamdata.teamsize.number;

    if (teamdata.teamsize.type != null) {
        document.getElementById("type").value = teamdata.teamsize.type;
    }

    suggestions = getAllSuggestions() || [];
    suggestions.sort(function (a, b) { return (a.vote > b.vote) ? -1 : ((a.vote < b.vote) ? 1 : 0); });
    suggestions.forEach(function (element, index) {

        success = false;
        if (index < amount) {
            success = true;
        }

        $('#panelContainer')
            .append(generateSuggestionPanel(element.id, "Vorschlag " + element.id, element.vote, success, element.ingredients));
    });
});

function getTemplates(){
    let templates;
    $.ajax({
        url: '/pizzas/templates/?teamname=' + getTeamname(),
        success: (res) => {
            templates = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return templates
}

function post(event){
    templates = getTemplates();
    id = event.target.id.substring(4,5);
    $.ajax({
        url: '/pizzas/suggestions',
        method: "POST",
        data: {
            teamname: getTeamname(),
            ingredients: templates[id].ingredients
        },
        success: () => {
            console.log("Your ingredients have been proposed.")
            window.location.reload(true);
        }
    })

    console.log(event.target);

}

function showTemplates() {

    templates = getTemplates();

    const buttonConcat = $('

')
    templates.forEach(function(element, index) {
        const ingredientsString = element.ingredients.reduce((acc, val) => {
               return acc + val + ", "
            }, "")
            .slice(0, -2);

        const lb = $(' ' + element.name + ': ');
        const ingr = $('' + ingredientsString + '')
        const btn = $('<button id="btn-' + index + '">/button>')
            .attr('type', 'button')
            .addClass('btn btn-success btn-xs')
            .text('auswählen')

        const btnPara = $('

')
        btnPara
            .append($('
'))
            .append(lb)
            .append($('
'))
            .append(ingr)
            .append($('
'))
            .append(btn)
            .append($('
'))

        buttonConcat.append(btnPara);
    });

    var buttonsAsString = $(buttonConcat).prop('outerHTML');

    swal({
        title: "Vorlagen",
        text: buttonsAsString,
        showCancelButton: true,
        showConfirmButton: false,
        html: buttonsAsString
    });

    templates.forEach(function(element, index) {
        document.getElementById("btn-"+index).addEventListener("click", post);
    });
}<commit_msg>Substitute global variables with local definitions<commit_after>function retrieveGetParameters() {
    let parameters = {}
    window.location.search
        .substring(1)                                       //Remove '?' at beginning
        .split('&')                                         //Split key-value pairs
        .map((currentElement) => {                          //Fill parameters object with key-value pairs
            const key = currentElement.split('=')[0];
            const value = currentElement.split('=')[1];
            parameters[key] = value;
        })
    return parameters;
}

function getTeamname() {
    const parameters = retrieveGetParameters();
    return parameters["teamname"];
}

function updateTeamname() {
    $('h1')
        .text(getTeamname())
    $('#teamname_input')
        .val(getTeamname());
    $('#teamname_for_selector')
        .val(getTeamname());
}

function getAllSuggestions() {
    let suggestions;
    $.ajax({
        url: '/pizzas/suggestions/?teamname=' + getTeamname(),
        success: (res) => {
            suggestions = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return suggestions
}

function vote(value, id) {
    const voteObject = {
        teamname: getTeamname(),
        id: id,
        vote: value
    };
    $.ajax({
        url: '/pizzas/suggestions/vote',
        data: voteObject,
        success: () => {
            console.log("Your vote has been sent to the server.")
            window.location.reload(true);
        },
        method: "POST"
    })
}

function voteHandler(event) {
    const storage = window.localStorage;
    const $eventTarget = event.target;
    const suggestionID = $(event.target).parent().parent().parent().parent().parent().parent().parent().attr('suggestion-id');
    if ($($eventTarget).hasClass('btn-success')
        && storage.getItem('voted_for_suggestion_id_' + suggestionID) !== 'positive') {
        //Send & save positive vote
        if (storage.getItem('voted_for_suggestion_id_' + suggestionID) === 'negative') {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'neutral');
        } else {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'positive');
        }
        vote(1, suggestionID);
    } else if ($($eventTarget).hasClass('btn-danger')
        && storage.getItem('voted_for_suggestion_id_' + suggestionID) !== 'negative') {
        //Send & save negative vote
        if (storage.getItem('voted_for_suggestion_id_' + suggestionID) === 'positive') {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'neutral');
        } else {
            storage.setItem('voted_for_suggestion_id_' + suggestionID, 'negative');
        }
        vote(-1, suggestionID);
    }
}

/**
 * Generate DOM structure for a pizza topping suggestion
 * @param id suggestion ID assigned by the backend for this suggestion
 * @param headingText text to be displayed in header of the suggestion
 * @param voteCount number of votes this suggestion has already received
 * @param success {boolean} whether this topping is currently successful and will be chose
 * @param ingredients list of ingredients used as pizza topping suggestion
 * @returns {*|jQuery}
 */
function generateSuggestionPanel(id, headingText, voteCount, success, ingredients) {
    const ingredientsList = ingredients
        .concat((new Array(4 - ingredients.length)).fill("-"))
        .reduce((acc, val) => {
            return acc + val + "
"
        }, "")
    const $headingRow = $('

')
        .addClass('row');
    const $h4 = $('

')
    const $panelHeadingText = $('

')
        .addClass('col-sm-6');
    const $panelHeadingVote = $('

')
        .addClass('col-sm-6')
    const $elementGroup = $('

')
        .addClass('element-group')

                                const $posButton = $('<button></button>')
                                    .attr('type', 'button')
                                    .addClass('btn btn-success btn-xs')
                                    .text('+')
                                    .click(voteHandler)
                                const $voteCount = $('')
                                    .addClass('label label-default label-sm')
                                    .text(voteCount)
                                const $negButton = $('<button></button>')
                                    .attr('type', 'button')
                                    .addClass('btn btn-danger btn-xs')
                                    .text('-')
                                    .click(voteHandler)

                            const $voteComponent = $($elementGroup)
                                .append($posButton)
                                .append($voteCount)
                                .append($negButton)

                        const $voteColumn = $($panelHeadingVote)
                            .append($voteComponent)

                        const $headingTextColumn = $($panelHeadingText)
                            .text(headingText)

                    const $header = $($h4)
                        .append($headingTextColumn)
                        .append($voteColumn)

                const $completeHeadingRow = $($headingRow)
                    .append($header)

            const $panelHeading = $('

')
                .addClass('panel-heading')
                .append($completeHeadingRow)

            const $panelBody = $('

')
                .addClass('panel-body')
                .html(ingredientsList.slice(0, -4))

        const $panel = $('

')
            .addClass('panel ' + ((success) ? 'panel-success' : 'panel-danger'))
            .append($panelHeading)
            .append($panelBody)

    const $panelColumn = $('

')
        .addClass('col-sm-4')
        .attr('suggestion-id', id)
        .append($panel)

    return $panelColumn;
}

function getAmount() {
    let amount;
    $.ajax({
        url: '/pizzas/amount/?teamname=' + getTeamname(),
        success: (res) => {
            amount = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return (amount) ? amount.amount : 0;
}

function getTeamdata() {
    let data;
    $.ajax({
        url: '/teams/data/?teamname=' + getTeamname(),
        success: (res) => {
            data = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return data
}

window.addEventListener('load', () => {
    updateTeamname();

    // multiplied by two, because here we work with half pizzas
    let amount = getAmount() * 2;

    let teamdata = getTeamdata();
    document.getElementById("count").value = teamdata.teamsize.number;

    if (teamdata.teamsize.type != null) {
        document.getElementById("type").value = teamdata.teamsize.type;
    }

    let suggestions = getAllSuggestions() || [];
    suggestions.sort(function (a, b) { return (a.vote > b.vote) ? -1 : ((a.vote < b.vote) ? 1 : 0); });
    suggestions.forEach(function (element, index) {

        let success = false;
        if (index < amount) {
            success = true;
        }

        $('#panelContainer')
            .append(generateSuggestionPanel(element.id, "Vorschlag " + element.id, element.vote, success, element.ingredients));
    });
});

function getTemplates(){
    let templates;
    $.ajax({
        url: '/pizzas/templates/?teamname=' + getTeamname(),
        success: (res) => {
            templates = JSON.parse(res);
        },
        method: "GET",
        async: false
    })
    return templates
}

function post(event){
    let templates = getTemplates();
    let id = event.target.id.substring(4,5);
    $.ajax({
        url: '/pizzas/suggestions',
        method: "POST",
        data: {
            teamname: getTeamname(),
            ingredients: templates[id].ingredients
        },
        success: () => {
            console.log("Your ingredients have been proposed.")
            window.location.reload(true);
        }
    })

    console.log(event.target);

}

function showTemplates() {

    templates = getTemplates();

    const buttonConcat = $('

')
    templates.forEach(function(element, index) {
        const ingredientsString = element.ingredients.reduce((acc, val) => {
               return acc + val + ", "
            }, "")
            .slice(0, -2);

        const lb = $(' ' + element.name + ': ');
        const ingr = $('' + ingredientsString + '')
        const btn = $('<button id="btn-' + index + '">/button>')
            .attr('type', 'button')
            .addClass('btn btn-success btn-xs')
            .text('auswählen')

        const btnPara = $('

')
        btnPara
            .append($('
'))
            .append(lb)
            .append($('
'))
            .append(ingr)
            .append($('
'))
            .append(btn)
            .append($('
'))

        buttonConcat.append(btnPara);
    });

    var buttonsAsString = $(buttonConcat).prop('outerHTML');

    swal({
        title: "Vorlagen",
        text: buttonsAsString,
        showCancelButton: true,
        showConfirmButton: false,
        html: buttonsAsString
    });

    templates.forEach(function(element, index) {
        document.getElementById("btn-"+index).addEventListener("click", post);
    });
}<|endoftext|>


<commit_before>if (typeof (require) != 'undefined') {
	var loader = require('./loader.js').loader;
}

loader.addModule('bTemplate',
'bAjax',
function (Ajax) {
	var c = {},
		_parseMatch,
		regexTemplate = /\[\[(.+?)]]/g,
		savedTemplates = {},
		_template,
		_url;

	_parseMatch = function (template) {
		function _parseExpression (expression, data) {
			var result = data, current;

			expression = expression.split('.');
			while (expression.length) {
				if ((current = expression.shift()) != '') {
					result = result[current];
				}
			}
			return result;
		}

		/*
		 * allowed commands:
		 * expression
		 * expression.attribute
		 * each expression on as loopElement template
		 * 		eg each users as user on userDesc
		 * if expression then template
		 */
		var regexExpression = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*$/g,
			regexEach = /^each\s+([a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\s+as\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s+on\s+([a-zA-Z_$\-0-9]+)$/,
			regexIf = /^if\s+([[a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\s+then\s+([a-zA-Z_$\-0-9]+)$/,
			match;

		if ((match = regexExpression.exec(template)) !== null) {
			return function (data) {
				var result = data, current;

				return _parseExpression(match[0], data);
			};
		}
		else if ((match = regexEach.exec(template)) !== null) {
			// match[1] -> data to loop on
			// match[2] -> name of the variable in each loop
			// match[3] -> template to display for each data loop
			return function (data) {
				var loopable = _parseExpression(match[1], data),
					d, result = '', dataLoop;

				if (typeof(loopable) != 'object') {
					throw "Object or array expected, " + typeof(loopable) + " got";
				}

				for (d in loopable) {
					dataLoop = {};
					dataLoop[match[2]] = loopable[d];
					result += c.compile(match[3], dataLoop);
				}

				return result;
			};

		}
		else if ((match = regexIf.exec(template)) !== null) {
			// match[1] -> data to test
			// match[2] -> template to display if the test is true
			return function (data) {
				var result = '', dataIf;
				if (_parseExpression(match[1], data)) {
					dataIf = {};
					dataIf[match[3]] = _parseExpression(match[1], data);
					result = c.compile(match[2], dataIf);
				}

				return result;
			};
		}
		else {
			throw "Invalid template expression \"" + template + "\"";
		}
	};

	_url = function (templateName, data, callback) {
		var html;
		Ajax.request(savedTemplates[templateName].url, {
			200: function (xhr) {
				html = xhr.responseText;
			}
		}, {}, 'GET', null, {async: false});
		savedTemplates[templateName].html = html;
	};

	_template = function (template, data) {
		template = template.replace(regexTemplate, function () {
			// parse match
			return _parseMatch(arguments[1])(data);
		});

		return template;
	}

	c.init = function (templates) {
		savedTemplates = templates;
	};

	c.compile = function (templateName, data, callback) {
		var compiledTemplate;
		if (!(templateName in savedTemplates)) {
			throw "Invalid template \"" + templateName + "\"";
		}
		else if ('url' in savedTemplates[templateName] && !('html' in savedTemplates[templateName])) {
			_url(templateName, data);
		}

		if ('html' in savedTemplates[templateName]) {
			compiledTemplate = _template(savedTemplates[templateName].html, data);
		}
		else {
			throw "A template needs at least an url or a html body";
		}

		if (callback) {
			callback(compiledTemplate);
		}
		else {
			return compiledTemplate;
		}
	}

	if (typeof (exports) != 'undefined') {
		exports.template = c;
	}

	return c;
});
<commit_msg>It is now possible to give some data to if<commit_after>if (typeof (require) != 'undefined') {
	var loader = require('./loader.js').loader;
}

loader.addModule('bTemplate',
'bAjax',
function (Ajax) {
	var c = {},
		_parseMatch,
		regexTemplate = /\[\[(.+?)]]/g,
		savedTemplates = {},
		_template,
		_url;

	_parseMatch = function (template) {
		function _parseExpression (expression, data) {
			var result = data, current;

			expression = expression.split('.');
			while (expression.length) {
				if ((current = expression.shift()) != '') {
					result = result[current];
				}
			}
			return result;
		}

		/*
		 * allowed commands:
		 * expression
		 * expression.attribute
		 * each expression on as loopElement template
		 * 		eg each users as user on userDesc
		 * if expression then template
		 */
		var regexExpression = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*$/g,
			regexEach = /^each\s+([a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\s+as\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s+on\s+([a-zA-Z_$\-0-9]+)$/,
			regexIf = /^if\s+([[a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\s+then\s+([a-zA-Z_$\-0-9]+)(?:\s+with\s+([a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*))?$/,
			match;

		if ((match = regexExpression.exec(template)) !== null) {
			return function (data) {
				var result = data, current;

				return _parseExpression(match[0], data);
			};
		}
		else if ((match = regexEach.exec(template)) !== null) {
			// match[1] -> data to loop on
			// match[2] -> name of the variable in each loop
			// match[3] -> template to display for each data loop
			return function (data) {
				var loopable = _parseExpression(match[1], data),
					d, result = '', dataLoop;

				if (typeof(loopable) != 'object') {
					throw "Object or array expected, " + typeof(loopable) + " got";
				}

				for (d in loopable) {
					dataLoop = {};
					dataLoop[match[2]] = loopable[d];
					result += c.compile(match[3], dataLoop);
				}

				return result;
			};

		}
		else if ((match = regexIf.exec(template)) !== null) {
			// match[1] -> data to test
			// match[2] -> template to display if the test is true
			// match[3] -> data to use in the template
			return function (data) {
				var result = '', dataIf;
				if (_parseExpression(match[1], data)) {
					dataIf = {};
					if (match[3] !== undefined) {
						dataIf[match[3]] = _parseExpression(match[3], data);
					}
					result = c.compile(match[2], dataIf);
				}

				return result;
			};
		}
		else {
			throw "Invalid template expression \"" + template + "\"";
		}
	};

	_url = function (templateName, data, callback) {
		var html;
		Ajax.request(savedTemplates[templateName].url, {
			200: function (xhr) {
				html = xhr.responseText;
			}
		}, {}, 'GET', null, {async: false});
		savedTemplates[templateName].html = html;
	};

	_template = function (template, data) {
		template = template.replace(regexTemplate, function () {
			// parse match
			return _parseMatch(arguments[1])(data);
		});

		return template;
	}

	c.init = function (templates) {
		savedTemplates = templates;
	};

	c.compile = function (templateName, data, callback) {
		var compiledTemplate;
		if (!(templateName in savedTemplates)) {
			throw "Invalid template \"" + templateName + "\"";
		}
		else if ('url' in savedTemplates[templateName] && !('html' in savedTemplates[templateName])) {
			_url(templateName, data);
		}

		if ('html' in savedTemplates[templateName]) {
			compiledTemplate = _template(savedTemplates[templateName].html, data);
		}
		else {
			throw "A template needs at least an url or a html body";
		}

		if (callback) {
			callback(compiledTemplate);
		}
		else {
			return compiledTemplate;
		}
	}

	if (typeof (exports) != 'undefined') {
		exports.template = c;
	}

	return c;
});
<|endoftext|>


<commit_before>(function($) {

function getPasteEvent() {
    var el = document.createElement('input'),
        name = 'onpaste';
    el.setAttribute(name, '');
    return (typeof el[name] === 'function')?'paste':'input';
}

var pasteEventName = getPasteEvent() + ".mask",
	ua = navigator.userAgent,
	iPhone = /iphone/i.test(ua),
	chrome = /chrome/i.test(ua),
	android=/android/i.test(ua),
	caretTimeoutId;

$.mask = {
	//Predefined character definitions
	definitions: {
		'9': "[0-9]",
		'a': "[A-Za-z]",
		'*': "[A-Za-z0-9]"
	},
	autoclear: true,
	dataName: "rawMaskFn",
	placeholder: '_'
};

$.fn.extend({
	//Helper Function for Caret positioning
	caret: function(begin, end) {
		var range;

		if (this.length === 0 || this.is(":hidden")) {
			return;
		}

		if (typeof begin == 'number') {
			end = (typeof end === 'number') ? end : begin;
			return this.each(function() {
				if (this.setSelectionRange) {
					this.setSelectionRange(begin, end);
				} else if (this.createTextRange) {
					range = this.createTextRange();
					range.collapse(true);
					range.moveEnd('character', end);
					range.moveStart('character', begin);
					range.select();
				}
			});
		} else {
			if (this[0].setSelectionRange) {
				begin = this[0].selectionStart;
				end = this[0].selectionEnd;
			} else if (document.selection && document.selection.createRange) {
				range = document.selection.createRange();
				begin = 0 - range.duplicate().moveStart('character', -100000);
				end = begin + range.text.length;
			}
			return { begin: begin, end: end };
		}
	},
	unmask: function() {
		return this.trigger("unmask");
	},
	mask: function(mask, settings) {
		var input,
			defs,
			tests,
			partialPosition,
			firstNonMaskPos,
			len;

		if (!mask && this.length > 0) {
			input = $(this[0]);
			return input.data($.mask.dataName)();
		}
		settings = $.extend({
			autoclear: $.mask.autoclear,
			placeholder: $.mask.placeholder, // Load default placeholder
			completed: null
		}, settings);

		defs = $.mask.definitions;
		tests = [];
		partialPosition = len = mask.length;
		firstNonMaskPos = null;

		$.each(mask.split(""), function(i, c) {
			if (c == '?') {
				len--;
				partialPosition = i;
			} else if (defs[c]) {
				tests.push(new RegExp(defs[c]));
				if (firstNonMaskPos === null) {
					firstNonMaskPos = tests.length - 1;
				}
			} else {
				tests.push(null);
			}
		});

		return this.trigger("unmask").each(function() {
			var input = $(this),
				buffer = $.map(
				mask.split(""),
				function(c, i) {
					if (c != '?') {
						return defs[c] ? settings.placeholder : c;
					}
				}),
				defaultBuffer = buffer.join(''),
				focusText = input.val();

			function seekNext(pos) {
				while (++pos < len && !tests[pos]);
				return pos;
			}

			function seekPrev(pos) {
				while (--pos >= 0 && !tests[pos]);
				return pos;
			}

			function shiftL(begin,end) {
				var i,
					j;

				if (begin<0) {
					return;
				}

				for (i = begin, j = seekNext(end); i < len; i++) {
					if (tests[i]) {
						if (j < len && tests[i].test(buffer[j])) {
							buffer[i] = buffer[j];
							buffer[j] = settings.placeholder;
						} else {
							break;
						}

						j = seekNext(j);
					}
				}
				writeBuffer();
				input.caret(Math.max(firstNonMaskPos, begin));
			}

			function shiftR(pos) {
				var i,
					c,
					j,
					t;

				for (i = pos, c = settings.placeholder; i < len; i++) {
					if (tests[i]) {
						j = seekNext(i);
						t = buffer[i];
						buffer[i] = c;
						if (j < len && tests[j].test(t)) {
							c = t;
						} else {
							break;
						}
					}
				}
			}

      function blurEvent(e) {
          checkVal();

          if (input.val() != focusText)
            input.change();
      }

			function keydownEvent(e) {
				var k = e.which,
					pos,
					begin,
					end;

				//backspace, delete, and escape get special treatment
				if (k === 8 || k === 46 || (iPhone && k === 127)) {
					pos = input.caret();
					begin = pos.begin;
					end = pos.end;

					if (end - begin === 0) {
						begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
						end=k===46?seekNext(end):end;
					}
					clearBuffer(begin, end);
					shiftL(begin, end - 1);

					e.preventDefault();
				} else if( k === 13 ) { // enter
					blurEvent.call(this, e);
				} else if (k === 27) { // escape
					input.val(focusText);
					input.caret(0, checkVal());
					e.preventDefault();
				}
			}

			function keypressEvent(e) {
				var k = e.which,
					pos = input.caret(),
					p,
					c,
					next;

                    if (k == 0) {
                        // unable to detect key pressed. Grab it from pos and adjust
                        // this is a failsafe for mobile chrome
                        // which can't detect keypress events
                        // reliably
                        if (pos.begin >= len) {
                            input.val(input.val().substr(0, len));
                            e.preventDefault();
                            return false;
                        }
                        if (pos.begin == pos.end) {
                            k = input.val().charCodeAt(pos.begin - 1);
                            pos.begin--;
                            pos.end--;
                        }
                    }

				if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
					return;
				} else if ( k && k !== 13 ) {
					if (pos.end - pos.begin !== 0){
						clearBuffer(pos.begin, pos.end);
						shiftL(pos.begin, pos.end-1);
					}

					p = seekNext(pos.begin - 1);
					if (p < len) {
						c = String.fromCharCode(k);
						if (tests[p].test(c)) {
							shiftR(p);

							buffer[p] = c;
							writeBuffer();
							next = seekNext(p);

							if(android){
								//Path for CSP Violation on FireFox OS 1.1
								var proxy = function()
								{
									$.proxy($.fn.caret,input,next);
								}

								setTimeout(proxy,0);
							}else{
								input.caret(next);
							}

							if (settings.completed && next >= len) {
								settings.completed.call(input);
							}
						}
					}
					e.preventDefault();
				}
			}

			function clearBuffer(start, end) {
				var i;
				for (i = start; i < end && i < len; i++) {
					if (tests[i]) {
						buffer[i] = settings.placeholder;
					}
				}
			}

			function writeBuffer() { input.val(buffer.join('')); }

			function checkVal(allow) {
				//try to place characters where they belong
				var test = input.val(),
					lastMatch = -1,
					i,
					c,
					pos;

				for (i = 0, pos = 0; i < len; i++) {
					if (tests[i]) {
						buffer[i] = settings.placeholder;
						while (pos++ < test.length) {
							c = test.charAt(pos - 1);
							if (tests[i].test(c)) {
								buffer[i] = c;
								lastMatch = i;
								break;
							}
						}
						if (pos > test.length) {
							break;
						}
					} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
						pos++;
						lastMatch = i;
					}
				}
				if (allow) {
					writeBuffer();
				} else if (lastMatch + 1 < partialPosition) {
					if (settings.autoclear || buffer.join('') === defaultBuffer) {
						// Invalid value. Remove it and replace it with the
						// mask, which is the default behavior.
						input.val("");
						clearBuffer(0, len);
					} else {
						// Invalid value, but we opt to show the value to the
						// user and allow them to correct their mistake.
						writeBuffer();
					}
				} else {
					writeBuffer();
					input.val(input.val().substring(0, lastMatch + 1));
				}
				return (partialPosition ? i : firstNonMaskPos);
			}

			input.data($.mask.dataName,function(){
				return $.map(buffer, function(c, i) {
					return tests[i]&&c!=settings.placeholder ? c : null;
				}).join('');
			});

			if (!input.attr("readonly"))
				input
				.one("unmask", function() {
					input
						.off(".mask")
						.removeData($.mask.dataName);
				})
				.on("focus.mask", function() {
					clearTimeout(caretTimeoutId);
					var pos;

					focusText = input.val();

					pos = checkVal();

					caretTimeoutId = setTimeout(function(){
						writeBuffer();
						if (pos == mask.replace("?","").length) {
							input.caret(0, pos);
						} else {
							input.caret(pos);
						}
					}, 10);
				})
				.on("blur.mask", blurEvent)
				.on("keydown.mask", keydownEvent)
				.on("keypress.mask", keypressEvent)
				.on(pasteEventName, function() {
					setTimeout(function() {
						var pos=checkVal(true);
						input.caret(pos);
						if (settings.completed && pos == input.val().length)
							settings.completed.call(input);
					}, 0);
				});
                if (chrome && android) {
                    input.on("keyup.mask", keypressEvent);
                }
				checkVal(); //Perform initial check for existing values
		});
	}
});
})(jQuery);
<commit_msg>Fix caret position on Android<commit_after>(function($) {

function getPasteEvent() {
    var el = document.createElement('input'),
        name = 'onpaste';
    el.setAttribute(name, '');
    return (typeof el[name] === 'function')?'paste':'input';
}

var pasteEventName = getPasteEvent() + ".mask",
	ua = navigator.userAgent,
	iPhone = /iphone/i.test(ua),
	chrome = /chrome/i.test(ua),
	android=/android/i.test(ua),
	caretTimeoutId;

$.mask = {
	//Predefined character definitions
	definitions: {
		'9': "[0-9]",
		'a': "[A-Za-z]",
		'*': "[A-Za-z0-9]"
	},
	autoclear: true,
	dataName: "rawMaskFn",
	placeholder: '_'
};

$.fn.extend({
	//Helper Function for Caret positioning
	caret: function(begin, end) {
		var range;

		if (this.length === 0 || this.is(":hidden")) {
			return;
		}

		if (typeof begin == 'number') {
			end = (typeof end === 'number') ? end : begin;
			return this.each(function() {
				if (this.setSelectionRange) {
					this.setSelectionRange(begin, end);
				} else if (this.createTextRange) {
					range = this.createTextRange();
					range.collapse(true);
					range.moveEnd('character', end);
					range.moveStart('character', begin);
					range.select();
				}
			});
		} else {
			if (this[0].setSelectionRange) {
				begin = this[0].selectionStart;
				end = this[0].selectionEnd;
			} else if (document.selection && document.selection.createRange) {
				range = document.selection.createRange();
				begin = 0 - range.duplicate().moveStart('character', -100000);
				end = begin + range.text.length;
			}
			return { begin: begin, end: end };
		}
	},
	unmask: function() {
		return this.trigger("unmask");
	},
	mask: function(mask, settings) {
		var input,
			defs,
			tests,
			partialPosition,
			firstNonMaskPos,
			len;

		if (!mask && this.length > 0) {
			input = $(this[0]);
			return input.data($.mask.dataName)();
		}
		settings = $.extend({
			autoclear: $.mask.autoclear,
			placeholder: $.mask.placeholder, // Load default placeholder
			completed: null
		}, settings);

		defs = $.mask.definitions;
		tests = [];
		partialPosition = len = mask.length;
		firstNonMaskPos = null;

		$.each(mask.split(""), function(i, c) {
			if (c == '?') {
				len--;
				partialPosition = i;
			} else if (defs[c]) {
				tests.push(new RegExp(defs[c]));
				if (firstNonMaskPos === null) {
					firstNonMaskPos = tests.length - 1;
				}
			} else {
				tests.push(null);
			}
		});

		return this.trigger("unmask").each(function() {
			var input = $(this),
				buffer = $.map(
				mask.split(""),
				function(c, i) {
					if (c != '?') {
						return defs[c] ? settings.placeholder : c;
					}
				}),
				defaultBuffer = buffer.join(''),
				focusText = input.val();

			function seekNext(pos) {
				while (++pos < len && !tests[pos]);
				return pos;
			}

			function seekPrev(pos) {
				while (--pos >= 0 && !tests[pos]);
				return pos;
			}

			function shiftL(begin,end) {
				var i,
					j;

				if (begin<0) {
					return;
				}

				for (i = begin, j = seekNext(end); i < len; i++) {
					if (tests[i]) {
						if (j < len && tests[i].test(buffer[j])) {
							buffer[i] = buffer[j];
							buffer[j] = settings.placeholder;
						} else {
							break;
						}

						j = seekNext(j);
					}
				}
				writeBuffer();
				input.caret(Math.max(firstNonMaskPos, begin));
			}

			function shiftR(pos) {
				var i,
					c,
					j,
					t;

				for (i = pos, c = settings.placeholder; i < len; i++) {
					if (tests[i]) {
						j = seekNext(i);
						t = buffer[i];
						buffer[i] = c;
						if (j < len && tests[j].test(t)) {
							c = t;
						} else {
							break;
						}
					}
				}
			}

      function blurEvent(e) {
          checkVal();

          if (input.val() != focusText)
            input.change();
      }

			function keydownEvent(e) {
				var k = e.which,
					pos,
					begin,
					end;

				//backspace, delete, and escape get special treatment
				if (k === 8 || k === 46 || (iPhone && k === 127)) {
					pos = input.caret();
					begin = pos.begin;
					end = pos.end;

					if (end - begin === 0) {
						begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
						end=k===46?seekNext(end):end;
					}
					clearBuffer(begin, end);
					shiftL(begin, end - 1);

					e.preventDefault();
				} else if( k === 13 ) { // enter
					blurEvent.call(this, e);
				} else if (k === 27) { // escape
					input.val(focusText);
					input.caret(0, checkVal());
					e.preventDefault();
				}
			}

			function keypressEvent(e) {
				var k = e.which,
					pos = input.caret(),
					p,
					c,
					next;

                    if (k == 0) {
                        // unable to detect key pressed. Grab it from pos and adjust
                        // this is a failsafe for mobile chrome
                        // which can't detect keypress events
                        // reliably
                        if (pos.begin >= len) {
                            input.val(input.val().substr(0, len));
                            e.preventDefault();
                            return false;
                        }
                        if (pos.begin == pos.end) {
                            k = input.val().charCodeAt(pos.begin - 1);
                            pos.begin--;
                            pos.end--;
                        }
                    }

				if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
					return;
				} else if ( k && k !== 13 ) {
					if (pos.end - pos.begin !== 0){
						clearBuffer(pos.begin, pos.end);
						shiftL(pos.begin, pos.end-1);
					}

					p = seekNext(pos.begin - 1);
					if (p < len) {
						c = String.fromCharCode(k);
						if (tests[p].test(c)) {
							shiftR(p);

							buffer[p] = c;
							writeBuffer();
							next = seekNext(p);

							if(android){
								//Path for CSP Violation on FireFox OS 1.1
								var proxy = function() {
									$.proxy($.fn.caret,input,next)();
								};

								setTimeout(proxy,0);
							}else{
								input.caret(next);
							}

							if (settings.completed && next >= len) {
								settings.completed.call(input);
							}
						}
					}
					e.preventDefault();
				}
			}

			function clearBuffer(start, end) {
				var i;
				for (i = start; i < end && i < len; i++) {
					if (tests[i]) {
						buffer[i] = settings.placeholder;
					}
				}
			}

			function writeBuffer() { input.val(buffer.join('')); }

			function checkVal(allow) {
				//try to place characters where they belong
				var test = input.val(),
					lastMatch = -1,
					i,
					c,
					pos;

				for (i = 0, pos = 0; i < len; i++) {
					if (tests[i]) {
						buffer[i] = settings.placeholder;
						while (pos++ < test.length) {
							c = test.charAt(pos - 1);
							if (tests[i].test(c)) {
								buffer[i] = c;
								lastMatch = i;
								break;
							}
						}
						if (pos > test.length) {
							break;
						}
					} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
						pos++;
						lastMatch = i;
					}
				}
				if (allow) {
					writeBuffer();
				} else if (lastMatch + 1 < partialPosition) {
					if (settings.autoclear || buffer.join('') === defaultBuffer) {
						// Invalid value. Remove it and replace it with the
						// mask, which is the default behavior.
						input.val("");
						clearBuffer(0, len);
					} else {
						// Invalid value, but we opt to show the value to the
						// user and allow them to correct their mistake.
						writeBuffer();
					}
				} else {
					writeBuffer();
					input.val(input.val().substring(0, lastMatch + 1));
				}
				return (partialPosition ? i : firstNonMaskPos);
			}

			input.data($.mask.dataName,function(){
				return $.map(buffer, function(c, i) {
					return tests[i]&&c!=settings.placeholder ? c : null;
				}).join('');
			});

			if (!input.attr("readonly"))
				input
				.one("unmask", function() {
					input
						.off(".mask")
						.removeData($.mask.dataName);
				})
				.on("focus.mask", function() {
					clearTimeout(caretTimeoutId);
					var pos;

					focusText = input.val();

					pos = checkVal();

					caretTimeoutId = setTimeout(function(){
						writeBuffer();
						if (pos == mask.replace("?","").length) {
							input.caret(0, pos);
						} else {
							input.caret(pos);
						}
					}, 10);
				})
				.on("blur.mask", blurEvent)
				.on("keydown.mask", keydownEvent)
				.on("keypress.mask", keypressEvent)
				.on(pasteEventName, function() {
					setTimeout(function() {
						var pos=checkVal(true);
						input.caret(pos);
						if (settings.completed && pos == input.val().length)
							settings.completed.call(input);
					}, 0);
				});
                if (chrome && android) {
                    input.on("keyup.mask", keypressEvent);
                }
				checkVal(); //Perform initial check for existing values
		});
	}
});
})(jQuery);
<|endoftext|>


<commit_before>/*
 * jQuery MultiSelect Plugin 0.4.1
 * Copyright (c) 2010 Eric Hynds
 *
 * http://www.erichynds.com/jquery/jquery-multiselect-plugin-with-themeroller-support/
 * Inspired by Cory S.N. LaViska's implementation, A Beautiful Site (http://abeautifulsite.net/) 2009
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
*/
(function($){

	$.fn.multiSelect = function(opts){
		opts = $.extend({}, $.fn.multiSelect.defaults, opts);

		return this.each(function(){
			new MultiSelect(this, opts);
		});
	};

	var MultiSelect = function(select,o){
		var $select = $original = $(select), $options, $labels, html = [], optgroups = [];

		html.push('');
		html.push('<input readonly="readonly" type="text" class="ui-state-default" value="'+ o.noneSelectedText +'" />');
		html.push('
');

		if(o.showHeader){
			html.push('
');
			html.push('
');
			html.push('
' + o.checkAllText + '
');
			html.push('
' + o.unCheckAllText + '
');
			html.push('

');
			html.push('
');
			html.push('
');
		};

		// build options
		html.push('
');
		$select.find('option').each(function(){
			var $this = $(this), title = $this.html(), value = this.value, len = value.length, $parent = $this.parent(), hasOptGroup = $parent.is('optgroup'), isDisabled = $this.is(':disabled'), labelClasses = ['ui-corner-all'], liClasses = [];

			if(hasOptGroup){
				var label = $parent.attr('label');

				if($.inArray(label,optgroups) === -1){
					html.push('
' + label + '
');
					optgroups.push(label);
				};
			};

			if(len > 0){
				if(isDisabled){
					labelClasses.push('ui-state-disabled');
					liClasses.push('ui-multiselect-disabled');
				};

				html.push('
');
				html.push('<input type="checkbox" name="' + $select.attr('name') + '" value="' + value + '" title="' + title + '"');
				if($this.is(':selected')){
					html.push(' checked="checked"');
				};
				if(isDisabled){
					html.push(' disabled="disabled"');
				};
				html.push(' />' + title + '
');
			};
		});
		html.push('

');

		// append the new control to the DOM; cache queries
		$select  = $select.after( html.join('') ).next('a.ui-multiselect');
		$options = $select.next('div.ui-multiselect-options');
		$header  = $options.find('div.ui-multiselect-header');
		$labels  = $options.find('label').not('.ui-state-disabled');

		// calculate widths
		var iconWidth = $select.find('span.ui-icon').outerWidth(), inputWidth = $original.outerWidth(), totalWidth = inputWidth+iconWidth;
		if( /\d/.test(o.minWidth) && totalWidth < o.minWidth){
			inputWidth = o.minWidth-iconWidth;
			totalWidth = o.minWidth;
		};

		// set widths
		$select.width(totalWidth).find('input').width(inputWidth);

		// build header links
		if(o.showHeader){
			$header.find('a').click(function(e){
				var $this = $(this);

				// close link
				if($this.hasClass('ui-multiselect-close')){
					$options.trigger('close');

				// check all / uncheck all
				} else {
					var checkAll = $this.hasClass('ui-multiselect-all');
					$options.trigger('toggleChecked', [(checkAll ? true : false)]);
					o[ checkAll ? 'onCheckAll' : 'onUncheckAll']['call'](this);
				};

				e.preventDefault();
			});
		};

		var updateSelected = function(){
			var $inputs = $labels.find('input'),
				$checked = $inputs.filter(':checked'),
				value = '',
				numChecked = $checked.length;

			if(numChecked === 0){
				value = o.noneSelectedText;
			} else {
				value = $.isFunction(o.selectedText)
					? o.selectedText.call(this, numChecked, $inputs.length, $checked.get())
					: o.selectedText.replace('#', numChecked).replace('#', $inputs.length);
			};

			$select.find('input').val(value).attr('title', value);
			return value;
		};

		// the select box events
		$select.bind({
			click: function(){
				$options.trigger('toggle');
			},
			keypress: function(e){
				switch(e.keyCode){
					case 27: // esc
					case 38: // up
						$options.trigger('close');
						break;
					case 40: // down
					case 0: // space
						$options.trigger('toggle');
						break;
				};
			},
			mouseenter: function(){
				if(!$select.hasClass('ui-state-disabled')){
					$(this).addClass('ui-state-hover');
				}
			},
			mouseleave: function(){
				$(this).removeClass('ui-state-hover');
			},
			focus: function(){
				if(!$select.hasClass('ui-state-disabled')){
					$(this).addClass('ui-state-focus');
				}
			},
			blur: function(){
				$(this).removeClass('ui-state-focus');
			}
		});

		// bind custom events to the options div
		$options.bind({
			'close': function(e, others){
				others = others || false;

				// hides all other options but the one clicked
				if(others === true){
					$('div.ui-multiselect-options')
					.filter(':visible')
					.fadeOut(o.fadeSpeed)
					.prev('a.ui-multiselect')
					.removeClass('ui-state-active')
					.trigger('mouseout');

				// hides the clicked options
				} else {
					$select.removeClass('ui-state-active').trigger('mouseout');
					$options.fadeOut(o.fadeSpeed);
				};
			},
			'open': function(e){

				// bail if this widget is disabled
				if($select.hasClass('ui-state-disabled')){
					return;
				}

				var offset = $select.position(), $container = $options.find('ul:last'), top, width;

				// calling select is active
				$select.addClass('ui-state-active');

				// hide all other options
				$options.trigger('close', [true]);

				// calculate positioning
				if(o.position === 'middle'){
					top = ( offset.top+($select.height()/2)-($options.outerHeight()/2) );
				} else if(o.position === 'top'){
					top = (offset.top-$options.outerHeight());
				} else {
					top = (offset.top+$select.outerHeight());
				};

				// calculate the width of the options menu
				width = $select.width()-parseInt($options.css('padding-left'))-parseInt($options.css('padding-right'));

				// select the first option
				$labels.filter('label:first').trigger('mouseenter').trigger('focus');

				// show the options div + position it
				$options.css({
					position: 'absolute',
					top: top+'px',
					left: offset.left+'px',
					width: width+'px'
				}).show();

				// set the scroll of the checkbox container
				$container.scrollTop(0);

				// set the height of the checkbox container
				if(o.maxHeight){
					$container.css('height', o.maxHeight);
				};

				o.onOpen.call($options[0]);
			},
			'toggle': function(){
				$options.trigger( $(this).is(':hidden') ? 'open' : 'close' );
			},
			'traverse': function(e, start, keycode){
				var $start = $(start),
					moveToLast = (keycode === 38 || keycode === 37) ? true : false,

					// select the first li that isn't an optgroup label / disabled
					$next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label)')[ moveToLast ? 'last' : 'first']();

				// if at the first/last element
				if(!$next.length){
					var $container = $options.find("ul:last");

					// move to the first/last
					$options.find('label')[ moveToLast ? 'last' : 'first' ]().trigger('mouseover');

					// set scroll position
					$container.scrollTop( moveToLast ? $container.height() : 0 );

				} else {
					$next.find('label').trigger('mouseenter');
				};
			},
			'toggleChecked': function(e, flag, group){
				var $inputs = (group && group.length) ? group : $labels.find('input');
				$inputs.not(':disabled').attr('checked', (flag ? 'checked' : ''));
				updateSelected();
			}
		})
		.find('li.ui-multiselect-optgroup-label a')
		.click(function(e){
			// optgroup label toggle support
			var $checkboxes = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('input');

			$options.trigger('toggleChecked', [ ($checkboxes.filter(':checked').length === $checkboxes.length) ? false : true, $checkboxes]);
			o.onOptgroupToggle.call(this, $checkboxes.get());
			e.preventDefault();
		});

		// labels/checkbox events
		$labels.bind({
			mouseenter: function(){
				$labels.removeClass('ui-state-hover');
				$(this).addClass('ui-state-hover').find('input').focus();
			},
			click: function(e,caller){
				// if the label was clicked, trigger the click event on the checkbox.  IE6 fix
				e.preventDefault();
				$(this).find('input').trigger('click', [true]);
			},
			keyup: function(e){
				switch(e.keyCode){
					case 27: // esc
						$options.trigger('close');
						break;

					case 38: // up
					case 40: // down
					case 37: // left
					case 39: // right
						$options.trigger('traverse', [this, e.keyCode]);
						break;

					case 13: // enter
						e.preventDefault();
						$(this).click();
						break;
				};
			}
		})
		.find('input')
		.bind('click', function(e, label){
			var $this = $(this);
			label = label || false;

			// stop this click from bubbling up to the label
			e.stopPropagation();

			// if the click originated from the label, stop the click event and manually toggle the checked state
			if(label){
				e.preventDefault();
				$this.attr('checked', $this.is(':checked') ? '' : 'checked');
			};

			o.onCheck.call(this);
			updateSelected();
		});

		// remove the original input element
		$original.remove();

		// apply bgiframe if available
		if($.fn.bgiframe){
			$options.bgiframe();
		};

		// update the number of selected elements when the page initially loads, and use that as the defaultValue.  necessary for form resets when options are pre-selected.
		$select.find('input')[0].defaultValue = updateSelected();
	};

	// close each select when clicking on any other element/anywhere else on the page
	$(document).bind('click', function(e){
		var $target = $(e.target);

		if(!$target.closest('div.ui-multiselect-options').length && !$target.parent().hasClass('ui-multiselect')){
			$('div.ui-multiselect-options').trigger('close', [true]);
		};
	});

	// default options
	$.fn.multiSelect.defaults = {
		showHeader: true,
		maxHeight: 175, /* max height of the checkbox container (scroll) in pixels */
		minWidth: 200, /* min width of the entire widget in pixels. setting to 'auto' will disable */
		checkAllText: 'Check all',
		unCheckAllText: 'Uncheck all',
		noneSelectedText: 'Select options',
		selectedText: '# selected',
		position: 'bottom', /* top|middle|bottom */
		shadow: false,
		fadeSpeed: 200,
		disabled: false,
		onCheck: function(){}, /* when an individual checkbox is clicked */
		onOpen: function(){}, /* when the select menu is opened */
		onCheckAll: function(){}, /* when the check all link is clicked */
		onUncheckAll: function(){}, /* when the uncheck all link is clicked */
		onOptgroupToggle: function(){} /* when the optgroup heading is clicked */
	};

})(jQuery);
<commit_msg>use this instead of $(this) where possible<commit_after>/*
 * jQuery MultiSelect Plugin 0.4.1
 * Copyright (c) 2010 Eric Hynds
 *
 * http://www.erichynds.com/jquery/jquery-multiselect-plugin-with-themeroller-support/
 * Inspired by Cory S.N. LaViska's implementation, A Beautiful Site (http://abeautifulsite.net/) 2009
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
*/
(function($){

	$.fn.multiSelect = function(opts){
		opts = $.extend({}, $.fn.multiSelect.defaults, opts);

		return this.each(function(){
			new MultiSelect(this, opts);
		});
	};

	var MultiSelect = function(select,o){
		var $select = $original = $(select), $options, $labels, html = [], optgroups = [];

		html.push('');
		html.push('<input readonly="readonly" type="text" class="ui-state-default" value="'+ o.noneSelectedText +'" />');
		html.push('
');

		if(o.showHeader){
			html.push('
');
			html.push('
');
			html.push('
' + o.checkAllText + '
');
			html.push('
' + o.unCheckAllText + '
');
			html.push('

');
			html.push('
');
			html.push('
');
		};

		// build options
		html.push('
');
		$select.find('option').each(function(){
			var $this = $(this), title = $this.html(), value = this.value, len = value.length, $parent = $this.parent(), hasOptGroup = $parent.is('optgroup'), isDisabled = $this.is(':disabled'), labelClasses = ['ui-corner-all'], liClasses = [];

			if(hasOptGroup){
				var label = $parent.attr('label');

				if($.inArray(label,optgroups) === -1){
					html.push('
' + label + '
');
					optgroups.push(label);
				};
			};

			if(len > 0){
				if(isDisabled){
					labelClasses.push('ui-state-disabled');
					liClasses.push('ui-multiselect-disabled');
				};

				html.push('
');
				html.push('<input type="checkbox" name="' + select.name + '" value="' + value + '" title="' + title + '"');
				if($this.is(':selected')){
					html.push(' checked="checked"');
				};
				if(isDisabled){
					html.push(' disabled="disabled"');
				};
				html.push(' />' + title + '
');
			};
		});
		html.push('

');

		// append the new control to the DOM; cache queries
		$select  = $select.after( html.join('') ).next('a.ui-multiselect');
		$options = $select.next('div.ui-multiselect-options');
		$header  = $options.find('div.ui-multiselect-header');
		$labels  = $options.find('label').not('.ui-state-disabled');

		// calculate widths
		var iconWidth = $select.find('span.ui-icon').outerWidth(), inputWidth = $original.outerWidth(), totalWidth = inputWidth+iconWidth;
		if( /\d/.test(o.minWidth) && totalWidth < o.minWidth){
			inputWidth = o.minWidth-iconWidth;
			totalWidth = o.minWidth;
		};

		// set widths
		$select.width(totalWidth).find('input').width(inputWidth);

		// build header links
		if(o.showHeader){
			$header.find('a').click(function(e){
				var $this = $(this);

				// close link
				if($this.hasClass('ui-multiselect-close')){
					$options.trigger('close');

				// check all / uncheck all
				} else {
					var checkAll = $this.hasClass('ui-multiselect-all');
					$options.trigger('toggleChecked', [(checkAll ? true : false)]);
					o[ checkAll ? 'onCheckAll' : 'onUncheckAll']['call'](this);
				};

				e.preventDefault();
			});
		};

		var updateSelected = function(){
			var $inputs = $labels.find('input'),
				$checked = $inputs.filter(':checked'),
				value = '',
				numChecked = $checked.length;

			if(numChecked === 0){
				value = o.noneSelectedText;
			} else {
				value = $.isFunction(o.selectedText)
					? o.selectedText.call(this, numChecked, $inputs.length, $checked.get())
					: o.selectedText.replace('#', numChecked).replace('#', $inputs.length);
			};

			$select.find('input').val(value).attr('title', value);
			return value;
		};

		// the select box events
		$select.bind({
			click: function(){
				$options.trigger('toggle');
			},
			keypress: function(e){
				switch(e.keyCode){
					case 27: // esc
					case 38: // up
						$options.trigger('close');
						break;
					case 40: // down
					case 0: // space
						$options.trigger('toggle');
						break;
				};
			},
			mouseenter: function(){
				if(!$select.hasClass('ui-state-disabled')){
					$(this).addClass('ui-state-hover');
				}
			},
			mouseleave: function(){
				$(this).removeClass('ui-state-hover');
			},
			focus: function(){
				if(!$select.hasClass('ui-state-disabled')){
					$(this).addClass('ui-state-focus');
				}
			},
			blur: function(){
				$(this).removeClass('ui-state-focus');
			}
		});

		// bind custom events to the options div
		$options.bind({
			'close': function(e, others){
				others = others || false;

				// hides all other options but the one clicked
				if(others === true){
					$('div.ui-multiselect-options')
					.filter(':visible')
					.fadeOut(o.fadeSpeed)
					.prev('a.ui-multiselect')
					.removeClass('ui-state-active')
					.trigger('mouseout');

				// hides the clicked options
				} else {
					$select.removeClass('ui-state-active').trigger('mouseout');
					$options.fadeOut(o.fadeSpeed);
				};
			},
			'open': function(e){

				// bail if this widget is disabled
				if($select.hasClass('ui-state-disabled')){
					return;
				}

				var offset = $select.position(), $container = $options.find('ul:last'), top, width;

				// calling select is active
				$select.addClass('ui-state-active');

				// hide all other options
				$options.trigger('close', [true]);

				// calculate positioning
				if(o.position === 'middle'){
					top = ( offset.top+($select.height()/2)-($options.outerHeight()/2) );
				} else if(o.position === 'top'){
					top = (offset.top-$options.outerHeight());
				} else {
					top = (offset.top+$select.outerHeight());
				};

				// calculate the width of the options menu
				width = $select.width()-parseInt($options.css('padding-left'))-parseInt($options.css('padding-right'));

				// select the first option
				$labels.filter('label:first').trigger('mouseenter').trigger('focus');

				// show the options div + position it
				$options.css({
					position: 'absolute',
					top: top+'px',
					left: offset.left+'px',
					width: width+'px'
				}).show();

				// set the scroll of the checkbox container
				$container.scrollTop(0);

				// set the height of the checkbox container
				if(o.maxHeight){
					$container.css('height', o.maxHeight);
				};

				o.onOpen.call($options[0]);
			},
			'toggle': function(){
				$options.trigger( $(this).is(':hidden') ? 'open' : 'close' );
			},
			'traverse': function(e, start, keycode){
				var $start = $(start),
					moveToLast = (keycode === 38 || keycode === 37) ? true : false,

					// select the first li that isn't an optgroup label / disabled
					$next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label)')[ moveToLast ? 'last' : 'first']();

				// if at the first/last element
				if(!$next.length){
					var $container = $options.find("ul:last");

					// move to the first/last
					$options.find('label')[ moveToLast ? 'last' : 'first' ]().trigger('mouseover');

					// set scroll position
					$container.scrollTop( moveToLast ? $container.height() : 0 );

				} else {
					$next.find('label').trigger('mouseenter');
				};
			},
			'toggleChecked': function(e, flag, group){
				var $inputs = (group && group.length) ? group : $labels.find('input');
				$inputs.not(':disabled').attr('checked', (flag ? 'checked' : ''));
				updateSelected();
			}
		})
		.find('li.ui-multiselect-optgroup-label a')
		.click(function(e){
			// optgroup label toggle support
			var $checkboxes = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('input');

			$options.trigger('toggleChecked', [ ($checkboxes.filter(':checked').length === $checkboxes.length) ? false : true, $checkboxes]);
			o.onOptgroupToggle.call(this, $checkboxes.get());
			e.preventDefault();
		});

		// labels/checkbox events
		$labels.bind({
			mouseenter: function(){
				$labels.removeClass('ui-state-hover');
				$(this).addClass('ui-state-hover').find('input').focus();
			},
			click: function(e,caller){
				// if the label was clicked, trigger the click event on the checkbox.  IE6 fix
				e.preventDefault();
				$(this).find('input').trigger('click', [true]);
			},
			keyup: function(e){
				switch(e.keyCode){
					case 27: // esc
						$options.trigger('close');
						break;

					case 38: // up
					case 40: // down
					case 37: // left
					case 39: // right
						$options.trigger('traverse', [this, e.keyCode]);
						break;

					case 13: // enter
						e.preventDefault();
						$(this).click();
						break;
				};
			}
		})
		.find('input')
		.bind('click', function(e, label){
			var $this = $(this);
			label = label || false;

			// stop this click from bubbling up to the label
			e.stopPropagation();

			// if the click originated from the label, stop the click event and manually toggle the checked state
			if(label){
				e.preventDefault();
				this.checked = this.checked ? false : true;
			};

			o.onCheck.call(this);
			updateSelected();
		});

		// remove the original input element
		$original.remove();

		// apply bgiframe if available
		if($.fn.bgiframe){
			$options.bgiframe();
		};

		// update the number of selected elements when the page initially loads, and use that as the defaultValue.  necessary for form resets when options are pre-selected.
		$select.find('input')[0].defaultValue = updateSelected();
	};

	// close each select when clicking on any other element/anywhere else on the page
	$(document).bind('click', function(e){
		var $target = $(e.target);

		if(!$target.closest('div.ui-multiselect-options').length && !$target.parent().hasClass('ui-multiselect')){
			$('div.ui-multiselect-options').trigger('close', [true]);
		};
	});

	// default options
	$.fn.multiSelect.defaults = {
		showHeader: true,
		maxHeight: 175, /* max height of the checkbox container (scroll) in pixels */
		minWidth: 200, /* min width of the entire widget in pixels. setting to 'auto' will disable */
		checkAllText: 'Check all',
		unCheckAllText: 'Uncheck all',
		noneSelectedText: 'Select options',
		selectedText: '# selected',
		position: 'bottom', /* top|middle|bottom */
		shadow: false,
		fadeSpeed: 200,
		disabled: false,
		onCheck: function(){}, /* when an individual checkbox is clicked */
		onOpen: function(){}, /* when the select menu is opened */
		onCheckAll: function(){}, /* when the check all link is clicked */
		onUncheckAll: function(){}, /* when the uncheck all link is clicked */
		onOptgroupToggle: function(){} /* when the optgroup heading is clicked */
	};

})(jQuery);
<|endoftext|>