<commit_before>/**
 * Unobtrusive scripting adapter for jQuery
 *
 * Requires jQuery 1.6.0 or later.
 * https://github.com/rails/jquery-ujs

 * Uploading file using rails.js
 * =============================
 *
 * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
 * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
 *
 * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
 *
 * Ex:
 *     $('form').live('ajax:aborted:file', function(event, elements){
 *       // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
 *       // Returning false in this handler tells rails.js to disallow standard form submission
 *       return false;
 *     });
 *
 * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
 *
 * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
 * techniques like the iframe method to upload the file instead.
 *
 * Required fields in rails.js
 * ===========================
 *
 * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
 * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
 *
 * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
 *
 * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
 *    get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
 *
 * Ex:
 *     $('form').live('ajax:aborted:required', function(event, elements){
 *       // Returning false in this handler tells rails.js to submit the form anyway.
 *       // The blank required inputs are passed to this function in `elements`.
 *       return ! confirm("Would you like to submit the form with missing info?");
 *     });
 */

(function($, undefined) {
  // Shorthand to make it a little easier to call public rails functions from within rails.js
  var rails;

  $.rails = rails = {
    // Link elements bound by jquery-ujs
    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',

		// Select elements bound by jquery-ujs
		inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',

    // Form elements bound by jquery-ujs
    formSubmitSelector: 'form',

    // Form input elements bound by jquery-ujs
    formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])',

    // Form input elements disabled during form submission
    disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',

    // Form input elements re-enabled after form submission
    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',

    // Form required input elements
    requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',

    // Form file input elements
    fileInputSelector: 'input:file',

    // Link onClick disable selector with possible reenable after remote submission
    linkDisableSelector: 'a[data-disable-with]',

    // Make sure that every Ajax request sends the CSRF token
    CSRFProtection: function(xhr) {
      var token = $('meta[name="csrf-token"]').attr('content');
      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    },

    // Triggers an event on an element and returns false if the event result is false
    fire: function(obj, name, data) {
      var event = $.Event(name);
      obj.trigger(event, data);
      return event.result !== false;
    },

    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
    confirm: function(message) {
      return confirm(message);
    },

    // Default ajax function, may be overridden with custom function in $.rails.ajax
    ajax: function(options) {
      return $.ajax(options);
    },

    // Submits "remote" forms and links with ajax
    handleRemote: function(element) {
      var method, url, data,
        crossDomain = element.data('cross-domain') || null,
        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
        options;

      if (rails.fire(element, 'ajax:before')) {

        if (element.is('form')) {
          method = element.attr('method');
          url = element.attr('action');
          data = element.serializeArray();
          // memoized value from clicked submit button
          var button = element.data('ujs:submit-button');
          if (button) {
            data.push(button);
            element.data('ujs:submit-button', null);
          }
        } else if (element.is(rails.inputChangeSelector)) {
          method = element.data('method');
          url = element.data('url');
          data = element.serialize();
          if (element.data('params')) data = data + "&" + element.data('params');
        } else {
          method = element.data('method');
          url = element.attr('href');
          data = element.data('params') || null;
        }

        options = {
          type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
          // stopping the "ajax:beforeSend" event will cancel the ajax request
          beforeSend: function(xhr, settings) {
            if (settings.dataType === undefined) {
              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
            }
            return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
          },
          success: function(data, status, xhr) {
            element.trigger('ajax:success', [data, status, xhr]);
          },
          complete: function(xhr, status) {
            element.trigger('ajax:complete', [xhr, status]);
          },
          error: function(xhr, status, error) {
            element.trigger('ajax:error', [xhr, status, error]);
          }
        };
        // Only pass url to `ajax` options if not blank
        if (url) { options.url = url; }

        rails.ajax(options);
      }
    },

    // Handles "data-method" on links such as:
    // Delete
    handleMethod: function(link) {
      var href = link.attr('href'),
        method = link.data('method'),
        target = link.attr('target'),
        csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content'),
        form = $('<form method="post" action="' + href + '"></form>'),
        metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';

      if (csrf_param !== undefined && csrf_token !== undefined) {
        metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
      }

      if (target) { form.attr('target', target); }

      form.hide().append(metadata_input).appendTo('body');
      form.submit();
    },

    /* Disables form elements:
      - Caches element value in 'ujs:enable-with' data store
      - Replaces element text with value of 'data-disable-with' attribute
      - Sets disabled property to true
    */
    disableFormElements: function(form) {
      form.find(rails.disableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        element.data('ujs:enable-with', element[method]());
        element[method](element.data('disable-with'));
        element.prop('disabled', true);
      });
    },

    /* Re-enables disabled form elements:
      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
      - Sets disabled property to false
    */
    enableFormElements: function(form) {
      form.find(rails.enableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
        element.prop('disabled', false);
      });
    },

   /* For 'data-confirm' attribute:
      - Fires `confirm` event
      - Shows the confirmation dialog
      - Fires the `confirm:complete` event

      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
   */
    allowAction: function(element) {
      var message = element.data('confirm'),
          answer = false, callback;
      if (!message) { return true; }

      if (rails.fire(element, 'confirm')) {
        answer = rails.confirm(message);
        callback = rails.fire(element, 'confirm:complete', [answer]);
      }
      return answer && callback;
    },

    // Helper function which checks for blank inputs in a form that match the specified CSS selector
    blankInputs: function(form, specifiedSelector, nonBlank) {
      var inputs = $(), input,
        selector = specifiedSelector || 'input,textarea';
      form.find(selector).each(function() {
        input = $(this);
        // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
        if (nonBlank ? input.val() : !input.val()) {
          inputs = inputs.add(input);
        }
      });
      return inputs.length ? inputs : false;
    },

    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
    nonBlankInputs: function(form, specifiedSelector) {
      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
    },

    // Helper function, needed to provide consistent behavior in IE
    stopEverything: function(e) {
      $(e.target).trigger('ujs:everythingStopped');
      e.stopImmediatePropagation();
      return false;
    },

    // find all the submit events directly bound to the form and
    // manually invoke them. If anyone returns false then stop the loop
    callFormSubmitBindings: function(form) {
      var events = form.data('events'), continuePropagation = true;
      if (events !== undefined && events['submit'] !== undefined) {
        $.each(events['submit'], function(i, obj){
          if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data);
        });
      }
      return continuePropagation;
    },

    //  replace element's html with the 'data-disable-with' after storing original html
    //  and prevent clicking on it
    disableElement: function(element) {
      element.data('ujs:enable-with', element.html()); // store enabled state
      element.html(element.data('disable-with')); // set to disabled state
      element.bind('click.railsDisable', function(e) { // prevent further clicking
        return rails.stopEverything(e)
      });
    },

    // restore element to its original state which was disabled by 'disableElement' above
    enableElement: function(element) {
      if (element.data('ujs:enable-with') !== undefined) {
        element.html(element.data('ujs:enable-with')); // set to old enabled state
        // this should be element.removeData('ujs:enable-with')
        // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
        element.data('ujs:enable-with', false); // clean up cache
      }
      element.unbind('click.railsDisable'); // enable element
    }

  };

  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

  $(rails.linkDisableSelector).live('ajax:complete', function() {
      rails.enableElement($(this));
  });

  $(rails.linkClickSelector).live('click.rails', function(e) {
    var link = $(this), method = link.data('method'), data = link.data('params');
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    if (link.is(rails.linkDisableSelector)) rails.disableElement(link);

    if (link.data('remote') !== undefined) {
      if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
      rails.handleRemote(link);
      return false;
    } else if (link.data('method')) {
      rails.handleMethod(link);
      return false;
    }
  });

	$(rails.inputChangeSelector).live('change.rails', function(e) {
    var link = $(this);
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    rails.handleRemote(link);
    return false;
  });

  $(rails.formSubmitSelector).live('submit.rails', function(e) {
    var form = $(this),
      remote = form.data('remote') !== undefined,
      blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
      nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);

    if (!rails.allowAction(form)) return rails.stopEverything(e);

    // skip other logic when required values are missing or file upload is present
    if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
      return rails.stopEverything(e);
    }

    if (remote) {
      if (nonBlankFileInputs) {
        return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
      }

      // If browser does not support submit bubbling, then this live-binding will be called before direct
      // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
      if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e);

      rails.handleRemote(form);
      return false;
    } else {
      // slight timeout so that the submit button gets properly serialized
      setTimeout(function(){ rails.disableFormElements(form); }, 13);
    }
  });

  $(rails.formInputClickSelector).live('click.rails', function(event) {
    var button = $(this);

    if (!rails.allowAction(button)) return rails.stopEverything(event);

    // register the pressed submit button
    var name = button.attr('name'),
      data = name ? {name:name, value:button.val()} : null;

    button.closest('form').data('ujs:submit-button', data);
  });

  $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
    if (this == event.target) rails.disableFormElements($(this));
  });

  $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) {
    if (this == event.target) rails.enableFormElements($(this));
  });

})( jQuery );
<commit_msg>Move the closure declaration above the usage comment so the comment will not get included in the compressed version of jquery_ujs produced by uglifier.<commit_after>(function($, undefined) {

/**
 * Unobtrusive scripting adapter for jQuery
 *
 * Requires jQuery 1.6.0 or later.
 * https://github.com/rails/jquery-ujs

 * Uploading file using rails.js
 * =============================
 *
 * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
 * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
 *
 * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
 *
 * Ex:
 *     $('form').live('ajax:aborted:file', function(event, elements){
 *       // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
 *       // Returning false in this handler tells rails.js to disallow standard form submission
 *       return false;
 *     });
 *
 * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
 *
 * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
 * techniques like the iframe method to upload the file instead.
 *
 * Required fields in rails.js
 * ===========================
 *
 * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
 * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
 *
 * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
 *
 * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
 *    get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
 *
 * Ex:
 *     $('form').live('ajax:aborted:required', function(event, elements){
 *       // Returning false in this handler tells rails.js to submit the form anyway.
 *       // The blank required inputs are passed to this function in `elements`.
 *       return ! confirm("Would you like to submit the form with missing info?");
 *     });
 */

  // Shorthand to make it a little easier to call public rails functions from within rails.js
  var rails;

  $.rails = rails = {
    // Link elements bound by jquery-ujs
    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',

		// Select elements bound by jquery-ujs
		inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',

    // Form elements bound by jquery-ujs
    formSubmitSelector: 'form',

    // Form input elements bound by jquery-ujs
    formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])',

    // Form input elements disabled during form submission
    disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',

    // Form input elements re-enabled after form submission
    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',

    // Form required input elements
    requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',

    // Form file input elements
    fileInputSelector: 'input:file',

    // Link onClick disable selector with possible reenable after remote submission
    linkDisableSelector: 'a[data-disable-with]',

    // Make sure that every Ajax request sends the CSRF token
    CSRFProtection: function(xhr) {
      var token = $('meta[name="csrf-token"]').attr('content');
      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    },

    // Triggers an event on an element and returns false if the event result is false
    fire: function(obj, name, data) {
      var event = $.Event(name);
      obj.trigger(event, data);
      return event.result !== false;
    },

    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
    confirm: function(message) {
      return confirm(message);
    },

    // Default ajax function, may be overridden with custom function in $.rails.ajax
    ajax: function(options) {
      return $.ajax(options);
    },

    // Submits "remote" forms and links with ajax
    handleRemote: function(element) {
      var method, url, data,
        crossDomain = element.data('cross-domain') || null,
        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
        options;

      if (rails.fire(element, 'ajax:before')) {

        if (element.is('form')) {
          method = element.attr('method');
          url = element.attr('action');
          data = element.serializeArray();
          // memoized value from clicked submit button
          var button = element.data('ujs:submit-button');
          if (button) {
            data.push(button);
            element.data('ujs:submit-button', null);
          }
        } else if (element.is(rails.inputChangeSelector)) {
          method = element.data('method');
          url = element.data('url');
          data = element.serialize();
          if (element.data('params')) data = data + "&" + element.data('params');
        } else {
          method = element.data('method');
          url = element.attr('href');
          data = element.data('params') || null;
        }

        options = {
          type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
          // stopping the "ajax:beforeSend" event will cancel the ajax request
          beforeSend: function(xhr, settings) {
            if (settings.dataType === undefined) {
              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
            }
            return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
          },
          success: function(data, status, xhr) {
            element.trigger('ajax:success', [data, status, xhr]);
          },
          complete: function(xhr, status) {
            element.trigger('ajax:complete', [xhr, status]);
          },
          error: function(xhr, status, error) {
            element.trigger('ajax:error', [xhr, status, error]);
          }
        };
        // Only pass url to `ajax` options if not blank
        if (url) { options.url = url; }

        rails.ajax(options);
      }
    },

    // Handles "data-method" on links such as:
    // Delete
    handleMethod: function(link) {
      var href = link.attr('href'),
        method = link.data('method'),
        target = link.attr('target'),
        csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content'),
        form = $('<form method="post" action="' + href + '"></form>'),
        metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';

      if (csrf_param !== undefined && csrf_token !== undefined) {
        metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
      }

      if (target) { form.attr('target', target); }

      form.hide().append(metadata_input).appendTo('body');
      form.submit();
    },

    /* Disables form elements:
      - Caches element value in 'ujs:enable-with' data store
      - Replaces element text with value of 'data-disable-with' attribute
      - Sets disabled property to true
    */
    disableFormElements: function(form) {
      form.find(rails.disableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        element.data('ujs:enable-with', element[method]());
        element[method](element.data('disable-with'));
        element.prop('disabled', true);
      });
    },

    /* Re-enables disabled form elements:
      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
      - Sets disabled property to false
    */
    enableFormElements: function(form) {
      form.find(rails.enableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
        element.prop('disabled', false);
      });
    },

   /* For 'data-confirm' attribute:
      - Fires `confirm` event
      - Shows the confirmation dialog
      - Fires the `confirm:complete` event

      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
   */
    allowAction: function(element) {
      var message = element.data('confirm'),
          answer = false, callback;
      if (!message) { return true; }

      if (rails.fire(element, 'confirm')) {
        answer = rails.confirm(message);
        callback = rails.fire(element, 'confirm:complete', [answer]);
      }
      return answer && callback;
    },

    // Helper function which checks for blank inputs in a form that match the specified CSS selector
    blankInputs: function(form, specifiedSelector, nonBlank) {
      var inputs = $(), input,
        selector = specifiedSelector || 'input,textarea';
      form.find(selector).each(function() {
        input = $(this);
        // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
        if (nonBlank ? input.val() : !input.val()) {
          inputs = inputs.add(input);
        }
      });
      return inputs.length ? inputs : false;
    },

    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
    nonBlankInputs: function(form, specifiedSelector) {
      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
    },

    // Helper function, needed to provide consistent behavior in IE
    stopEverything: function(e) {
      $(e.target).trigger('ujs:everythingStopped');
      e.stopImmediatePropagation();
      return false;
    },

    // find all the submit events directly bound to the form and
    // manually invoke them. If anyone returns false then stop the loop
    callFormSubmitBindings: function(form) {
      var events = form.data('events'), continuePropagation = true;
      if (events !== undefined && events['submit'] !== undefined) {
        $.each(events['submit'], function(i, obj){
          if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data);
        });
      }
      return continuePropagation;
    },

    //  replace element's html with the 'data-disable-with' after storing original html
    //  and prevent clicking on it
    disableElement: function(element) {
      element.data('ujs:enable-with', element.html()); // store enabled state
      element.html(element.data('disable-with')); // set to disabled state
      element.bind('click.railsDisable', function(e) { // prevent further clicking
        return rails.stopEverything(e)
      });
    },

    // restore element to its original state which was disabled by 'disableElement' above
    enableElement: function(element) {
      if (element.data('ujs:enable-with') !== undefined) {
        element.html(element.data('ujs:enable-with')); // set to old enabled state
        // this should be element.removeData('ujs:enable-with')
        // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
        element.data('ujs:enable-with', false); // clean up cache
      }
      element.unbind('click.railsDisable'); // enable element
    }

  };

  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

  $(rails.linkDisableSelector).live('ajax:complete', function() {
      rails.enableElement($(this));
  });

  $(rails.linkClickSelector).live('click.rails', function(e) {
    var link = $(this), method = link.data('method'), data = link.data('params');
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    if (link.is(rails.linkDisableSelector)) rails.disableElement(link);

    if (link.data('remote') !== undefined) {
      if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
      rails.handleRemote(link);
      return false;
    } else if (link.data('method')) {
      rails.handleMethod(link);
      return false;
    }
  });

	$(rails.inputChangeSelector).live('change.rails', function(e) {
    var link = $(this);
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    rails.handleRemote(link);
    return false;
  });

  $(rails.formSubmitSelector).live('submit.rails', function(e) {
    var form = $(this),
      remote = form.data('remote') !== undefined,
      blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
      nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);

    if (!rails.allowAction(form)) return rails.stopEverything(e);

    // skip other logic when required values are missing or file upload is present
    if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
      return rails.stopEverything(e);
    }

    if (remote) {
      if (nonBlankFileInputs) {
        return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
      }

      // If browser does not support submit bubbling, then this live-binding will be called before direct
      // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
      if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e);

      rails.handleRemote(form);
      return false;
    } else {
      // slight timeout so that the submit button gets properly serialized
      setTimeout(function(){ rails.disableFormElements(form); }, 13);
    }
  });

  $(rails.formInputClickSelector).live('click.rails', function(event) {
    var button = $(this);

    if (!rails.allowAction(button)) return rails.stopEverything(event);

    // register the pressed submit button
    var name = button.attr('name'),
      data = name ? {name:name, value:button.val()} : null;

    button.closest('form').data('ujs:submit-button', data);
  });

  $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
    if (this == event.target) rails.disableFormElements($(this));
  });

  $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) {
    if (this == event.target) rails.enableFormElements($(this));
  });

})( jQuery );
<|endoftext|>


<commit_before>var app = angular.module('matrix', []);
var hash_params = L.Hash.parseHash(location.hash);
var mode_mapping = {
  'foot' : 'pedestrian',
  'car' : 'auto',
  'bicycle' : 'bicycle'
};

var serviceUrl = "https://matrix.mapzen.com/";
var envServer = "production";
var envToken = accessToken.prod;
var sentManyToManyEnd = false;

function selectEnv() {
  $("option:selected").each(function() {
    envServer = $(this).text();
    serviceUrl = document.getElementById(envServer).value;
    getEnvToken();
  });
}

function getEnvToken() {
  switch (envServer) {
  case "localhost":
    envToken = accessToken.local;
    break;
  case "development":
    envToken = accessToken.dev;
    break;
  case "production":
    envToken = accessToken.prod;
    break;
  }
}

app.run(function($rootScope) {
  var hash_loc = hash_params ? hash_params : {
    'center' : {
      'lat' : 40.7486,
      'lng' : -73.9690
    },
    'zoom' : 13
  };
  $rootScope.geobase = {
    'zoom' : hash_loc.zoom,
    'lat' : hash_loc.center.lat,
    'lon' : hash_loc.center.lng
  }
  $(document).on('new-location', function(e) {
    $rootScope.geobase = {
      'zoom' : e.zoom,
      'lat' : e.lat,
      'lon' : e.lon
    };
  })
});

//hooks up to the div whose data-ng-controller attribute matches this name
app.controller('MatrixController', function($scope, $rootScope, $sce, $http) {
  var roadmap = L.tileLayer('http://b.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution : '© OpenStreetMap contributers'
  }), tangramZinc = Tangram.leafletLayer({
    scene: 'https://raw.githubusercontent.com/tangrams/zinc-style-no-labels/gh-pages/zinc-style-no-labels.yaml',
    attribution: 'Tangram | © OSM contributors | Mapzen',
  }), cyclemap = L.tileLayer('http://b.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', {
    attribution : 'Maps © Thunderforest, ;Data © OpenStreetMap contributors'
  }), elevationmap = L.tileLayer('http://b.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png', {
    attribution : 'Maps © Thunderforest, ;Data © OpenStreetMap contributors'
  });

  var baseMaps = {
    "TangramZinc" : tangramZinc,
    "RoadMap" : roadmap,
    "CycleMap" : cyclemap,
    "ElevationMap" : elevationmap
  };

  //leaflet slippy map
  var map = L.map('map', {
    zoom : $rootScope.geobase.zoom,
    zoomControl : true,
    layers : [ tangramZinc ],
    center : [ $rootScope.geobase.lat, $rootScope.geobase.lon ]
  });

  // Add geocoding plugin
  var options = {
    layers: 'coarse'
  };

  L.control.geocoder('search-8LtGSDw', options).addTo(map);
  L.control.layers(baseMaps, null).addTo(map);

  // If iframed, we're going to have to disable some of the touch interaction
  // to not hijack page scroll. See Stamen's Checklist for Maps: http://content.stamen.com/stamens-checklist-for-maps
  if (window.self !== window.top) {
    map.scrollWheelZoom.disable();
  }

  var getOriginIcon = function() {
    return new L.Icon({
      iconUrl : '../matrix/resource/matrix_pin_start.png',
      iconSize : [ 30, 36 ],
      shadowUrl: null
    })
  };

  var getDestinationIcon = function() {
    return new L.Icon({
      iconUrl : '../matrix/resource/matrix_pin_end.png',
      iconSize : [ 30, 36 ],
      shadowUrl: null
    });
  };

 //Number of locations
  var locations = 0;
  var markers = [];

  var locateMarkers = [];
  var remove_markers = function() {
    for (i = 0; i < markers.length; i++) {
      map.removeLayer(markers[i]);
    }
    markers = [];
    locateMarkers.forEach(function (element, index, array) {
      map.removeLayer(element);
    });
    locateMarkers = [];
  };

  $rootScope.$on('map.setView', function(ev, geo, zoom) {
    map.setView(geo, zoom || 8);
  });

  $rootScope.$on('map.dropOriginMarker', function(ev, geo, locCount) {

      var marker = new L.marker(geo, {
          icon : getOriginIcon()
          }).bindLabel((locCount).toString(), (locCount < 10) ? {
          position: [geo.lat,geo.lon],
          noHide: true,
          offset: [-9,-12]
          } : {
          position: [geo.lat,geo.lon],
          noHide: true,
          offset: [-12,-12]
          }
        );
    map.addLayer(marker);
    markers.push(marker);
  });

  $rootScope.$on('map.dropDestMarker', function(ev, geo, locCount) {

      var marker = new L.marker(geo, {
        icon : getDestinationIcon()
      }).bindLabel((locCount).toString(), (locCount < 10) ? {
        position: [geo.lat,geo.lon],
        noHide: true,
        offset: [-9,-12]
      } : {
        position: [geo.lat,geo.lon],
        noHide: true,
        offset: [-13,-12]
        }
      );
    map.addLayer(marker);
    markers.push(marker);
  });

  $scope.renderHtml = function(html_code) {
    return $sce.trustAsHtml(html_code);
  };

  $scope.setMode = function(mode){
    $scope.mode = mode;
  }

  var reset_form = function() {
    $scope.startPoints = [];
    $scope.endPoints = [];
  };

  //set up map events
  chooseLocations();

  var oneToMany = document.getElementById("one_to_many");
  var manyToOne = document.getElementById("many_to_one");
  var manyToMany = document.getElementById("many_to_many");
  var clearBtn = document.getElementById("clear_btn");
  var matrixBtn = document.getElementById("matrix_btn");

  $scope.mode = 'auto';
  $scope.matrixType = '';
  $scope.startPoints = [];
  $scope.endPoints = [];
  $scope.editingFocus = 'start_points'
  $scope.appView = 'control'

  $scope.backToControlView = function(e) {
    $scope.appView = 'control';
    $('#columns').columns('destroy');
  }

  $scope.clearAll = function(e) {
    $scope.matrixType = '';
    $scope.startPoints = [];
    $scope.endPoints = [];
    $scope.appView = 'control'
    $scope.editingFocus = 'start_points'
    sentManyToManyEnd = false
    remove_markers();
    locations = 0;
    counterText = 0;
    markers = [];
    remove_markers();
    $('#columns').columns('destroy');
  }

  $scope.goToEndPoints = function(e) {
    $scope.editingFocus = 'end_points'
  }

  $scope.oneToManyClick = function(e) {
    $scope.matrixType = "one_to_many";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add a point";
    $scope.end_mapInstruction = " Click on the map to add points";
    $scope.startgeocode = "lat, long";
    $scope.endgeocode = "lat, long";
    getEnvToken();
  }

  $scope.manyToOneClick = function(e) {
    $scope.matrixType = "many_to_one";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add points";
    $scope.end_mapInstruction = " Click on the map to add a point";
    getEnvToken();
  };

  $scope.manyToManyClick = function(e) {
    $scope.matrixType = "many_to_many";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add points";
    getEnvToken();
  };

  matrixBtn.addEventListener('click', function(e) {
    var waypoints = [];
    var locationsArray = $scope.startPoints.concat($scope.endPoints);

    locationsArray.forEach(function(gLoc) {
      waypoints.push(L.latLng(gLoc.lat, gLoc.lon));
    });

    var  matrix = L.Matrix.widget(envToken, $scope.mode, $scope.matrixType);
    matrix.matrix({
      waypoints : waypoints
    });
    $scope.appView = 'matrixTable'
    $scope.$apply();
  });

//locate edge snap markers
  var locateEdgeMarkers = function (locate_result) {
    // clear it
    locateMarkers.forEach(function (element, index, array) {
      map.removeLayer(element);
    });
    locateMarkers = []

    //mark from node
    if(locate_result.node != null) {
      var marker = L.circle( [locate_result.node.lat,locate_result.node.lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
      map.addLayer(marker);
      var popup = L.popup({maxHeight : 200});
      popup.setContent("
" + JSON.stringify(locate_result, null, 2) + "
");
      marker.bindPopup(popup).openPopup();
      locateMarkers.push(marker);
    }//mark all the results for that spot
    else if(locate_result.edges != null) {
      locate_result.edges.forEach(function (element, index, array) {
        var marker = L.circle( [element.correlated_lat, element.correlated_lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
        map.addLayer(marker);
        var popup = L.popup({maxHeight : 200});
        popup.setContent("
" + JSON.stringify(element, null, 2) + "
");
        marker.bindPopup(popup).openPopup();
        locateMarkers.push(marker);
      });
    }//no data probably
    else {
      var marker = L.circle( [locate_result.input_lat,locate_result.input_lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
      map.addLayer(marker);
      var popup = L.popup({maxHeight : 200});
      popup.setContent("
" + JSON.stringify(locate_result, null, 2) + "
");
      marker.bindPopup(popup).openPopup();
      locateMarkers.push(marker);
    }
  };

  var counterText = 0;

  function chooseLocations() {
    map.on('click', function(e) {
    if ($scope.matrixType == '')
      alert("Please select a matrix type.");

    var geo = {
      'lat' : e.latlng.lat.toFixed(6),
      'lon' : e.latlng.lng.toFixed(6)
    };

    var eventObj = window.event ? event : e.originalEvent;
    var latlon = "";
    if ($scope.matrixType == "one_to_many") {
      if (locations == 0) {
        $scope.editingFocus = 'end_points';
        $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], 0);
        locations++;
        latlon = geo.lat + ' , '+ geo.lon;
        $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        return;
      } else {
        counterText++;
        $rootScope.$emit('map.dropDestMarker', [ geo.lat, geo.lon ], counterText);
        locations++;
        latlon = geo.lat + ' , '+ geo.lon;
        $scope.endPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon,latlon: latlon});
        $scope.$apply();
        return;
      }
    } else if ($scope.matrixType == "many_to_one") {
      if ($scope.editingFocus == 'end_points' ) {
        if (sentManyToManyEnd == false) {
        sentManyToManyEnd = true;
        $rootScope.$emit('map.dropDestMarker', [ geo.lat, geo.lon ], counterText);
        locations++;

        latlon = geo.lat + ' , '+ geo.lon;
        $scope.endPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        return;
        } else {
          alert("Only 1 end point should be selected for a Many-to-One.");
        }
      } else {

        $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], counterText);
        locations++;

        latlon = geo.lat + ' , '+ geo.lon;
        $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        counterText++;
        return;
      }
      //many_to_many
    } else if ($scope.matrixType == "many_to_many") {

      $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], counterText);
      locations++;

      var latlon = geo.lat + ' , '+ geo.lon;
      $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon,latlon: latlon});
      $scope.$apply();
      counterText++;
      locations++;
      return;
    }
    });
  };
    // ask the service for information about this location
    map.on("contextmenu", function(e) {
      var ll = {
        lat : e.latlng.lat,
        lon : e.latlng.lng
      };
      getEnvToken();
      var locate = L.locate(envToken);
      locate.locate(ll, locateEdgeMarkers);
    });

})<commit_msg>updated tangram map layer to be Cinnabar<commit_after>var app = angular.module('matrix', []);
var hash_params = L.Hash.parseHash(location.hash);
var mode_mapping = {
  'foot' : 'pedestrian',
  'car' : 'auto',
  'bicycle' : 'bicycle'
};

var serviceUrl = "https://matrix.mapzen.com/";
var envServer = "production";
var envToken = accessToken.prod;
var sentManyToManyEnd = false;

function selectEnv() {
  $("option:selected").each(function() {
    envServer = $(this).text();
    serviceUrl = document.getElementById(envServer).value;
    getEnvToken();
  });
}

function getEnvToken() {
  switch (envServer) {
  case "localhost":
    envToken = accessToken.local;
    break;
  case "development":
    envToken = accessToken.dev;
    break;
  case "production":
    envToken = accessToken.prod;
    break;
  }
}

app.run(function($rootScope) {
  var hash_loc = hash_params ? hash_params : {
    'center' : {
      'lat' : 40.7486,
      'lng' : -73.9690
    },
    'zoom' : 13
  };
  $rootScope.geobase = {
    'zoom' : hash_loc.zoom,
    'lat' : hash_loc.center.lat,
    'lon' : hash_loc.center.lng
  }
  $(document).on('new-location', function(e) {
    $rootScope.geobase = {
      'zoom' : e.zoom,
      'lat' : e.lat,
      'lon' : e.lon
    };
  })
});

//hooks up to the div whose data-ng-controller attribute matches this name
app.controller('MatrixController', function($scope, $rootScope, $sce, $http) {
  var roadmap = L.tileLayer('http://b.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution : '© OpenStreetMap contributers'
  }), tangramCinnabar = Tangram.leafletLayer({
    scene: 'https://raw.githubusercontent.com/tangrams/cinnabar-style-more-labels/gh-pages/cinnabar-style-more-labels.yaml',
    attribution: 'Tangram | © OSM contributors | Mapzen',
  }), cyclemap = L.tileLayer('http://b.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', {
    attribution : 'Maps © Thunderforest, ;Data © OpenStreetMap contributors'
  }), elevationmap = L.tileLayer('http://b.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png', {
    attribution : 'Maps © Thunderforest, ;Data © OpenStreetMap contributors'
  });

  var baseMaps = {
    "TangramCinnabar" : tangramCinnabar,
    "RoadMap" : roadmap,
    "CycleMap" : cyclemap,
    "ElevationMap" : elevationmap
  };

  //leaflet slippy map
  var map = L.map('map', {
    zoom : $rootScope.geobase.zoom,
    zoomControl : true,
    layers : [ tangramCinnabar ],
    center : [ $rootScope.geobase.lat, $rootScope.geobase.lon ]
  });

  // Add geocoding plugin
  var options = {
    layers: 'coarse'
  };

  L.control.geocoder('search-8LtGSDw', options).addTo(map);
  L.control.layers(baseMaps, null).addTo(map);

  // If iframed, we're going to have to disable some of the touch interaction
  // to not hijack page scroll. See Stamen's Checklist for Maps: http://content.stamen.com/stamens-checklist-for-maps
  if (window.self !== window.top) {
    map.scrollWheelZoom.disable();
  }

  var getOriginIcon = function() {
    return new L.Icon({
      iconUrl : '../matrix/resource/matrix_pin_start.png',
      iconSize : [ 30, 36 ],
      shadowUrl: null
    })
  };

  var getDestinationIcon = function() {
    return new L.Icon({
      iconUrl : '../matrix/resource/matrix_pin_end.png',
      iconSize : [ 30, 36 ],
      shadowUrl: null
    });
  };

 //Number of locations
  var locations = 0;
  var markers = [];

  var locateMarkers = [];
  var remove_markers = function() {
    for (i = 0; i < markers.length; i++) {
      map.removeLayer(markers[i]);
    }
    markers = [];
    locateMarkers.forEach(function (element, index, array) {
      map.removeLayer(element);
    });
    locateMarkers = [];
  };

  $rootScope.$on('map.setView', function(ev, geo, zoom) {
    map.setView(geo, zoom || 8);
  });

  $rootScope.$on('map.dropOriginMarker', function(ev, geo, locCount) {

      var marker = new L.marker(geo, {
          icon : getOriginIcon()
          }).bindLabel((locCount).toString(), (locCount < 10) ? {
          position: [geo.lat,geo.lon],
          noHide: true,
          offset: [-9,-12]
          } : {
          position: [geo.lat,geo.lon],
          noHide: true,
          offset: [-12,-12]
          }
        );
    map.addLayer(marker);
    markers.push(marker);
  });

  $rootScope.$on('map.dropDestMarker', function(ev, geo, locCount) {

      var marker = new L.marker(geo, {
        icon : getDestinationIcon()
      }).bindLabel((locCount).toString(), (locCount < 10) ? {
        position: [geo.lat,geo.lon],
        noHide: true,
        offset: [-9,-12]
      } : {
        position: [geo.lat,geo.lon],
        noHide: true,
        offset: [-13,-12]
        }
      );
    map.addLayer(marker);
    markers.push(marker);
  });

  $scope.renderHtml = function(html_code) {
    return $sce.trustAsHtml(html_code);
  };

  $scope.setMode = function(mode){
    $scope.mode = mode;
  }

  var reset_form = function() {
    $scope.startPoints = [];
    $scope.endPoints = [];
  };

  //set up map events
  chooseLocations();

  var oneToMany = document.getElementById("one_to_many");
  var manyToOne = document.getElementById("many_to_one");
  var manyToMany = document.getElementById("many_to_many");
  var clearBtn = document.getElementById("clear_btn");
  var matrixBtn = document.getElementById("matrix_btn");

  $scope.mode = 'auto';
  $scope.matrixType = '';
  $scope.startPoints = [];
  $scope.endPoints = [];
  $scope.editingFocus = 'start_points'
  $scope.appView = 'control'

  $scope.backToControlView = function(e) {
    $scope.appView = 'control';
    $('#columns').columns('destroy');
  }

  $scope.clearAll = function(e) {
    $scope.matrixType = '';
    $scope.startPoints = [];
    $scope.endPoints = [];
    $scope.appView = 'control'
    $scope.editingFocus = 'start_points'
    sentManyToManyEnd = false
    remove_markers();
    locations = 0;
    counterText = 0;
    markers = [];
    remove_markers();
    $('#columns').columns('destroy');
  }

  $scope.goToEndPoints = function(e) {
    $scope.editingFocus = 'end_points'
  }

  $scope.oneToManyClick = function(e) {
    $scope.matrixType = "one_to_many";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add a point";
    $scope.end_mapInstruction = " Click on the map to add points";
    $scope.startgeocode = "lat, long";
    $scope.endgeocode = "lat, long";
    getEnvToken();
  }

  $scope.manyToOneClick = function(e) {
    $scope.matrixType = "many_to_one";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add points";
    $scope.end_mapInstruction = " Click on the map to add a point";
    getEnvToken();
  };

  $scope.manyToManyClick = function(e) {
    $scope.matrixType = "many_to_many";
    reset_form();
    $scope.start_mapInstruction = " Click on the map to add points";
    getEnvToken();
  };

  matrixBtn.addEventListener('click', function(e) {
    var waypoints = [];
    var locationsArray = $scope.startPoints.concat($scope.endPoints);

    locationsArray.forEach(function(gLoc) {
      waypoints.push(L.latLng(gLoc.lat, gLoc.lon));
    });

    var  matrix = L.Matrix.widget(envToken, $scope.mode, $scope.matrixType);
    matrix.matrix({
      waypoints : waypoints
    });
    $scope.appView = 'matrixTable'
    $scope.$apply();
  });

//locate edge snap markers
  var locateEdgeMarkers = function (locate_result) {
    // clear it
    locateMarkers.forEach(function (element, index, array) {
      map.removeLayer(element);
    });
    locateMarkers = []

    //mark from node
    if(locate_result.node != null) {
      var marker = L.circle( [locate_result.node.lat,locate_result.node.lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
      map.addLayer(marker);
      var popup = L.popup({maxHeight : 200});
      popup.setContent("
" + JSON.stringify(locate_result, null, 2) + "
");
      marker.bindPopup(popup).openPopup();
      locateMarkers.push(marker);
    }//mark all the results for that spot
    else if(locate_result.edges != null) {
      locate_result.edges.forEach(function (element, index, array) {
        var marker = L.circle( [element.correlated_lat, element.correlated_lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
        map.addLayer(marker);
        var popup = L.popup({maxHeight : 200});
        popup.setContent("
" + JSON.stringify(element, null, 2) + "
");
        marker.bindPopup(popup).openPopup();
        locateMarkers.push(marker);
      });
    }//no data probably
    else {
      var marker = L.circle( [locate_result.input_lat,locate_result.input_lon], 2, { color: '#444', opacity: 1, fill: true, fillColor: '#eee', fillOpacity: 1 });
      map.addLayer(marker);
      var popup = L.popup({maxHeight : 200});
      popup.setContent("
" + JSON.stringify(locate_result, null, 2) + "
");
      marker.bindPopup(popup).openPopup();
      locateMarkers.push(marker);
    }
  };

  var counterText = 0;

  function chooseLocations() {
    map.on('click', function(e) {
    if ($scope.matrixType == '')
      alert("Please select a matrix type.");

    var geo = {
      'lat' : e.latlng.lat.toFixed(6),
      'lon' : e.latlng.lng.toFixed(6)
    };

    var eventObj = window.event ? event : e.originalEvent;
    var latlon = "";
    if ($scope.matrixType == "one_to_many") {
      if (locations == 0) {
        $scope.editingFocus = 'end_points';
        $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], 0);
        locations++;
        latlon = geo.lat + ' , '+ geo.lon;
        $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        return;
      } else {
        counterText++;
        $rootScope.$emit('map.dropDestMarker', [ geo.lat, geo.lon ], counterText);
        locations++;
        latlon = geo.lat + ' , '+ geo.lon;
        $scope.endPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon,latlon: latlon});
        $scope.$apply();
        return;
      }
    } else if ($scope.matrixType == "many_to_one") {
      if ($scope.editingFocus == 'end_points' ) {
        if (sentManyToManyEnd == false) {
        sentManyToManyEnd = true;
        $rootScope.$emit('map.dropDestMarker', [ geo.lat, geo.lon ], counterText);
        locations++;

        latlon = geo.lat + ' , '+ geo.lon;
        $scope.endPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        return;
        } else {
          alert("Only 1 end point should be selected for a Many-to-One.");
        }
      } else {

        $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], counterText);
        locations++;

        latlon = geo.lat + ' , '+ geo.lon;
        $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon, latlon: latlon});
        $scope.$apply();
        counterText++;
        return;
      }
      //many_to_many
    } else if ($scope.matrixType == "many_to_many") {

      $rootScope.$emit('map.dropOriginMarker', [ geo.lat, geo.lon ], counterText);
      locations++;

      var latlon = geo.lat + ' , '+ geo.lon;
      $scope.startPoints.push({index: (counterText), lat:geo.lat, lon: geo.lon,latlon: latlon});
      $scope.$apply();
      counterText++;
      locations++;
      return;
    }
    });
  };
    // ask the service for information about this location
    map.on("contextmenu", function(e) {
      var ll = {
        lat : e.latlng.lat,
        lon : e.latlng.lng
      };
      getEnvToken();
      var locate = L.locate(envToken);
      locate.locate(ll, locateEdgeMarkers);
    });

})<|endoftext|>


<commit_before>(function(){

  var version = '0.1'
    , boards = {}
    , board = {}
    , pins = []
    , pin_count = 0
    , delay = 1000
    , timer;

  // make sure user is logged in and on profile page
  if (location.hostname.match(/pinterest.com$/) && $ && $('.usernameLink').length > 0) {
    if ($('.boardLinkWrapper').length > 0) {
      // already on profile page
      start();
    } else {
      // send to profile page
      var profile_link = ($('.thumbUserInfo a, .BoardCount').length > 0) ? $('.thumbUserInfo a, .BoardCount') : $('.usernameLink');
      var selected_board = location.pathname;
      $(document).bind('DOMNodeInserted', function(e) {
        // wait for boards to be loaded
        if ($(e.target).find('.boardLinkWrapper').length > 0) {
          $(document).unbind('DOMNodeInserted');
          start(selected_board);
        }
      });
      profile_link.click();
    }
  } else {
    alert('Log in and visit your profile to start (pinterest.com/username)');
    return false;
  }

  // show overlay
  function start(selected_board) {

    // check if overlay is already open
    if ($('#pboverlay').length > 0) return false;

    // start at top of page
    window.scrollTo(0,0);

    $('body').append('\

\
\
×\
<form class="standardForm">\

Choose a board to export
\

\
    <select></select>\
    <button class="Button btn rounded primary">\
      Export\
    </button>\

\

Pinback v'+version+'
\
</form>\

');

    var select = $('#pboverlay select');
    select.append($("<option>", {
      text: ' - All public pins - ',
      value: '/' + location.pathname.split('/')[1] + '/pins/',
      data: {name: 'all public pins'}
    }));

    $('.boardLinkWrapper').each(function() {
      var name = $.trim($(this).find('.boardName').text());
      select.append($("<option>", {
        text: name,
        value: $(this).attr('href'),
        data: {name: name},
        selected: (selected_board == $(this).attr('href'))
      }));
    });

    $('#pboverlay button').on('click', function(e) {
      e.preventDefault();
      var name = select.find('option:selected').data('name');
      $('a[href="'+select.val()+'"]').attr('href',select.val()+'?'+Date.now()).click();
      status('Exporting ' + name + '...');
      $('#pboverlay .controls').html('<meter min="0" max="100"></meter>');
    });

    $('#pboverlay .close').on('click', function(e) {
      e.preventDefault();
      window.scrollTo(0,0);
      location.href = location.pathname;
    });

    // watch for ajax
    $(document).ajaxComplete(function(event, xhr, settings) {
      if (!xhr.responseJSON) return false;
      // regular resources
      if (xhr.responseJSON.resource && xhr.responseJSON.resource_response && xhr.responseJSON.resource_response.data) {
        parse_resource(xhr.responseJSON.resource, xhr.responseJSON.resource_response.data);
      }
      // cached resources
      else if (xhr.responseJSON.resource_data_cache) {
        $.each(xhr.responseJSON.resource_data_cache, function() {
          if (this.resource) parse_resource(this.resource, this.data);
        });
      }
    });
  }

  // parse incoming ajax responses
  function parse_resource(r, data) {
    if (r.name == 'UserResource' || r.name == 'BoardResource') {
      board = data;
      pins = [];
    }
    if (r.name == 'UserPinsResource' || r.name == 'BoardFeedResource') {
      $.each(data, function() {
        if (this.id) {
          // add board if not already
          if (!boards[this.board.name]) boards[this.board.name] = {
            id: this.board.id,
            name: this.board.name,
            url: "https://www.pinterest.com"+this.board.url,
            privacy: this.board.privacy,
            pins: []
          }
          // add pin to board
          boards[this.board.name].pins.push({
            id: this.id,
            link: this.link,
            description: $.trim(this.description),
            url: "https://www.pinterest.com/pin/"+this.id,
            image: this.images.orig.url,
            color: this.dominant_color,
            longitude: (this.place && this.place.longitude) || null,
            latitude: (this.place && this.place.latitude) || null,
            pinner: this.pinner.username,
            privacy: this.privacy,
            date: Date.parse(this.created_at)
          });
          pin_count++;
        }
      });

      progress(pin_count, board.pin_count);

      // scroll page to find more pins
      var next = (typeof(r.options.bookmarks) !== "undefined") ? r.options.bookmarks[0] : null;
      if (timer) clearTimeout(timer);
      if (pin_count >= board.pin_count || next == '-end-') {
        // found the end
        if (pin_count > 0) done();
      } else {
        // keep going
        timer = setTimeout(function() {
          $("html, body").animate({ scrollTop: document.body.scrollHeight }, delay);
        }, delay);
      }
    }
  }

  function status(s) {
    $('#pboverlay h1').text(s);
  }

  function progress(a, b) {
    status('Exporting ' + a + " of " + b + '...');
    $('#pboverlay meter').val((a/b)*100);
  }

  function privacy(p) {
    // borrowed PRIVATE attribute format from Delicious
    return (p != 'public') ? 1 : 0;
  }

  function escapeHtml(unsafe) {
    return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
  }

  // generate Netscape bookmark file
  function done() {

    status('Export complete!');

    // template header
    var data = '<!DOCTYPE NETSCAPE-Bookmark-file-1>\n\
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">\n\
<TITLE>Bookmarks</TITLE>\n\

Bookmarks
\n\

\n';

    $.each(boards, function() {
      // board
      data += '

'+escapeHtml(this.name)+'
\n

\n';

      // pins
      $.each(this.pins, function(i, p) {
        data += '
'+escapeHtml(p.description)+'\n';
      });

      // board footer
      data += '

\n';
    });

    // template footer
    data += '

';

    // create file
    var filename = location.pathname.replace(/^\/|\/$/g, '').replace(/\//g,'-')+".html";
    var blob = new Blob([data], {type: 'text/html'});
    var url = URL.createObjectURL(blob);

    // add save button
    $('#pboverlay .controls').html('Save export file');

    // add note for browsers that don't support download attribute
    if (typeof(document.createElement('a').download) === "undefined") {
      $('#pboverlay .controls a').on('click', function() {
        alert('Choose File > Save As in your browser to save a copy of your export');
      });
    }
  }

})();
<commit_msg>Update pinback.js to v0.2<commit_after>(function(){

  var version = '0.2'
    , boards = {}
    , board = {}
    , selected_board
    , pins = []
    , pin_count = 0
    , delay = 1000
    , timer;

  // make sure user is logged in and on profile page
  if (location.hostname.match(/pinterest.com$/) && document.querySelector('.usernameLink')) {
    if (document.querySelector('.boardLinkWrapper')) {
      // already on profile page
      start();
    } else {
      // send to profile page
      var profile_link = document.querySelector('.profileSource, .BoardCount, .usernameLink');
      selected_board = location.href;
      document.addEventListener('DOMNodeInserted', listenForProfile);
      profile_link.click();
    }
  } else {
    alert('Log in and visit your profile to start (pinterest.com/username)');
    return false;
  }

  // wait for profile to load
  function listenForProfile() {
    if (document.querySelector('.boardLinkWrapper')) {
      document.removeEventListener('DOMNodeInserted', listenForProfile);
      start();
    }
  }

  // show overlay
  function start() {

    // check if overlay is already open
    if (document.querySelector('#pboverlay')) return false;

    // start at top of page
    window.scrollTo(0,0);

    // create overlay
    var overlay = document.createElement('div');
    overlay.id = 'pboverlay';
    overlay.innerHTML = '\
\
×\
<form class="standardForm">\

Choose a board to export
\

\
    <select></select>\
    <button class="Button btn rounded primary">\
      Export\
    </button>\

\

Pinback v'+version+'
\
</form>';

    // add overlay to document
    document.querySelector('body').appendChild(overlay);

    // find boards
    var select = document.querySelector('#pboverlay select');
    var option = document.createElement('option');
    option.text = ' - All public pins - ';
    option.value = '/' + location.pathname.split('/')[1] + '/pins/';
    option.dataset.name = 'all public pins';
    select.add(option);

    Array.prototype.forEach.call(document.querySelectorAll('.boardLinkWrapper'), function(el, i) {
      var name = el.querySelector('.name').innerText;
      var option = document.createElement('option');
      option.text = name;
      option.value = el.getAttribute('href');
      option.dataset.name = name;
      option.selected = (selected_board == el.href);
      select.add(option);
    });

    document.querySelector('#pboverlay button').onclick = function() {
      var selected = select.querySelector('option:checked');
      var name = selected.dataset.name;

      var link = document.querySelector('a[href="'+selected.value+'"]');
      link.href = selected.value+'?'+Date.now();
      link.click();

      status('Exporting ' + name + '...');
      document.querySelector('#pboverlay .controls').innerHTML = '<meter min="0" max="100"></meter>';

      return false;
    };

    document.querySelector('#pboverlay .close').onclick = function() {
      window.scrollTo(0,0);
      location.href = location.pathname;
      return false;
    };

    // watch for ajax
    XMLHttpRequest.prototype.send = (function(orig){
      return function(){

        this.addEventListener('loadend', function(e){
          if (e.target.getResponseHeader('content-type').match(/json/)) {
            var json = JSON.parse(e.target.response);
            // regular resources
            if (json.resource && json.resource_response && json.resource_response.data) {
              parse_resource(json.resource, json.resource_response.data);
            }
            // cached resources
            else if (json.resource_data_cache) {
              json.resource_data_cache.forEach(function(el, i) {
                if (el.resource) parse_resource(el.resource, el.data);
              });
            }
          }
        }, false);

        return orig.apply(this, arguments);
      };
    })(XMLHttpRequest.prototype.send);
  }

  // parse incoming ajax responses
  function parse_resource(r, data) {
    if (r.name == 'UserResource' || r.name == 'BoardResource') {
      board = data;
      pins = [];
    }
    if (r.name == 'UserPinsResource' || r.name == 'BoardFeedResource') {
      data.forEach(function(p, i) {
        if (p.type == 'pin') {
          // add board if not already
          if (!boards[p.board.name]) boards[p.board.name] = {
            id:           p.board.id,
            name:         p.board.name,
            url:          "https://www.pinterest.com"+p.board.url,
            privacy:      p.board.privacy,
            pins:         []
          }
          // add pin to board
          boards[p.board.name].pins.push({
            id:           p.id,
            link:         p.link,
            description:  p.description,
            url:          "https://www.pinterest.com/pin/"+p.id,
            image:        p.images.orig.url,
            color:        p.dominant_color,
            longitude:    (p.place && p.place.longitude) || null,
            latitude:     (p.place && p.place.latitude) || null,
            pinner:       p.pinner.username,
            privacy:      p.privacy,
            date:         Date.parse(p.created_at)
          });
          pin_count++;
        }
      });

      progress(pin_count, board.pin_count);

      // scroll page to find more pins
      var next = (typeof(r.options.bookmarks) !== "undefined") ? r.options.bookmarks[0] : null;
      if (timer) clearTimeout(timer);
      if (pin_count >= board.pin_count || next == '-end-') {
        // found the end
        if (pin_count > 0) done();
      } else {
        // keep going
        timer = setTimeout(function() {
          window.scrollTo(0, document.body.scrollHeight);
        }, delay);
      }
    }
  }

  function status(s) {
    document.querySelector('#pboverlay h1').innerText = s;
  }

  function progress(a, b) {
    status('Exporting ' + a + " of " + b + '...');
    document.querySelector('#pboverlay meter').value = (a/b)*100;
  }

  function privacy(p) {
    // borrowed PRIVATE attribute format from Delicious
    return (p != 'public') ? 1 : 0;
  }

  function escapeHtml(unsafe) {
    return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
  }

  // generate Netscape bookmark file
  function done() {

    status('Export complete!');

    // template header
    var data = '<!DOCTYPE NETSCAPE-Bookmark-file-1>\n\
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">\n\
<TITLE>Bookmarks</TITLE>\n\

Bookmarks
\n\

\n';

    for (var name in boards) {
      var b = boards[name];
      // board
      data += '

'+escapeHtml(b.name)+'
\n

\n';

      // pins
      b.pins.forEach(function(p, i) {
        data += '
'+escapeHtml(p.description)+'\n';
      });

      // board footer
      data += '

\n';
    }

    // template footer
    data += '

';

    // create file
    var filename = location.pathname.replace(/^\/|\/$/g, '').replace(/\//g,'-')+".html";
    var blob = new Blob([data], {type: 'text/html'});
    var url = URL.createObjectURL(blob);

    // add save button
    document.querySelector('#pboverlay .controls').innerHTML = 'Save export file';

    // add note for browsers that don't support download attribute
    if (typeof(document.createElement('a').download) === "undefined") {
      document.querySelector('#pboverlay .controls a').onclick = function() {
        alert('Choose File > Save As in your browser to save a copy of your export');
      }
    }
  }

})();
<|endoftext|>


<commit_before>#!/usr/bin/env node
/*jslint node:true, strict:false */

var program = require('commander'),
	Table = require('cli-table'),
	Set = require('simplesets').Set,
	fs = require('fs'),
	path = require('path'),
	jsp = require("uglify-js").parser,
	pro = require("uglify-js").uglify,
	JessieFunction = require('../builder/libs/jessie/Function.js'),
	JessieRendition = require('../builder/libs/jessie/Rendition.js'),
	JessieConstructorFn = require('../builder/libs/jessie/ConstructorFn.js'),
	JessiePrototypeMethod = require('../builder/libs/jessie/PrototypeMethod.js'),
	JessieFunctionSet = require('../builder/libs/jessie/FunctionSet.js'),
	JessieConstructorFnSet = require('../builder/libs/jessie/ConstructorFnSet.js'),
	JessieBuilder = require('../builder/libs/jessie/Builder.js'),
	functionSet = new JessieFunctionSet('../functions/', JessieFunction, JessieRendition),
	constructorFnSet = new JessieConstructorFnSet('../constructors/', JessieConstructorFn, JessiePrototypeMethod),
	requestedFunctions = [],
	requestedConstructorFns = [],
	buildOptions = {
		headerPath: '../libraries/header1.inc',
		footerPath: '../libraries/footer1.inc'
	},
	builder = null,
	response = null,
	output = null,
	firstError,
	message;

program
	.version('0.0.1')
	.usage('[options] <functions ...>')
	.option('-o, --output [file]', 'The file to output to (outputs to stdout by default)')
	.option('--minify', 'Minify the output using UglifyJS')
	.option('--namespace [name]', 'The name of the global variable to export', "jessie")
	.parse(process.argv);

functionSet.create();
constructorFnSet.create();

// if no args are used, output the help
if(program.args.length === 0) {
	process.stdout.write(program.helpInformation());
	program.emit('--help');
	process.exit(0);
}

setupRequestedFunctions();

if(program.minify) {
	buildOptions.minify = true;
}

if(program.namespace) {
	buildOptions.namespace = program.namespace.trim();
}

builder = new JessieBuilder(functionSet, constructorFnSet, requestedFunctions, requestedConstructorFns, buildOptions);
response = builder.build();

if(response.success) {
	if(program.output) {
		output = fs.createWriteStream(program.output);
		output.once('open', function(){
			output.write(response.output);
		});
	} else {
		process.stdout.write(response.output);
	}
}
else {
	firstError = response.errors[0];
	message = "" + firstError.itemName + ": depends on " + firstError.dependency;
	throw new Error(message);
}

// output some info to stderr
process.stderr.write("\n========================================================\n\n");
process.stderr.write("Jessie build successful!\n\n");
process.stderr.write("The following functions have been included:\n\n");

requestedFunctions.forEach(function(requestedFunction, i) {
	process.stderr.write(""+(i+1));
	process.stderr.write(".\t");
	process.stderr.write(requestedFunction.functionName);
	process.stderr.write("\n");
});

if(program.output) {
	process.stderr.write("\n");
	process.stderr.write("File written to: " + program.output + ".");
	process.stderr.write("\n");
}

process.stderr.write("\nEnjoy and happy cross-browser scripting!");
process.stderr.write("\n\n========================================================\n\n");

function list(val) {
	return val.split(',');
}

function setupRequestedFunctions() {
	// parse the args to get a map of functions that we need to output
	program.args.forEach(function(arg){
		// each argument can specify the renditions that are required
		// by separating them with colons
		arg = arg.split(":");

		// first part is the function name
		var functionName = arg[0];

		// second part is the renditionId (1 or 2 or 3 etc)
		var renditionId = arg[1];

		if(!renditionId) {
			throw new Error("You must specify a particular rendition e.g. "+functionName+":1");
		}

		if(arg.length > 2) {
			throw new Error("Only one rendition allowed");
		}

		requestedFunctions.push({
			functionName: functionName,
			renditionId: parseInt(renditionId, 10)
		});

		if(functionSet.getFunctionByName(functionName) === null) {
			throw new Error("Could not find function: " + functionName);
		}
	});
}<commit_msg>Added parameter and shortcut to --minify option on cli. Set a default of level 3 if a parameter is not included. Changed the buildOptions to minification so it is picked up by the builder and the minification is performed<commit_after>#!/usr/bin/env node
/*jslint node:true, strict:false */

var program = require('commander'),
	Table = require('cli-table'),
	Set = require('simplesets').Set,
	fs = require('fs'),
	path = require('path'),
	jsp = require("uglify-js").parser,
	pro = require("uglify-js").uglify,
	JessieFunction = require('../builder/libs/jessie/Function.js'),
	JessieRendition = require('../builder/libs/jessie/Rendition.js'),
	JessieConstructorFn = require('../builder/libs/jessie/ConstructorFn.js'),
	JessiePrototypeMethod = require('../builder/libs/jessie/PrototypeMethod.js'),
	JessieFunctionSet = require('../builder/libs/jessie/FunctionSet.js'),
	JessieConstructorFnSet = require('../builder/libs/jessie/ConstructorFnSet.js'),
	JessieBuilder = require('../builder/libs/jessie/Builder.js'),
	functionSet = new JessieFunctionSet('../functions/', JessieFunction, JessieRendition),
	constructorFnSet = new JessieConstructorFnSet('../constructors/', JessieConstructorFn, JessiePrototypeMethod),
	requestedFunctions = [],
	requestedConstructorFns = [],
	buildOptions = {
		headerPath: '../libraries/header1.inc',
		footerPath: '../libraries/footer1.inc'
	},
	builder = null,
	response = null,
	output = null,
	firstError,
	message;

program
	.version('0.0.1')
	.usage('[options] <functions ...>')
	.option('-o, --output [file]', 'The file to output to (outputs to stdout by default)')
	.option('-m, --minify [level]', 'Minify the output using UglifyJS')
	.option('--namespace [name]', 'The name of the global variable to export', "jessie")
	.parse(process.argv);

functionSet.create();
constructorFnSet.create();

// if no args are used, output the help
if(program.args.length === 0) {
	process.stdout.write(program.helpInformation());
	program.emit('--help');
	process.exit(0);
}

setupRequestedFunctions();

if(program.minify) {
	//default to level 3 if a parameter is not included
	var minifyLevel = ('boolean' == typeof program.minify) ? 3 : program.minify;

	buildOptions.minification = 'level' + minifyLevel;
}

if(program.namespace) {
	buildOptions.namespace = program.namespace.trim();
}

builder = new JessieBuilder(functionSet, constructorFnSet, requestedFunctions, requestedConstructorFns, buildOptions);
response = builder.build();

if(response.success) {
	if(program.output) {
		output = fs.createWriteStream(program.output);
		output.once('open', function(){
			output.write(response.output);
		});
	} else {
		process.stdout.write(response.output);
	}
}
else {
	firstError = response.errors[0];
	message = "" + firstError.itemName + ": depends on " + firstError.dependency;
	throw new Error(message);
}

// output some info to stderr
process.stderr.write("\n========================================================\n\n");
process.stderr.write("Jessie build successful!\n\n");
process.stderr.write("The following functions have been included:\n\n");

requestedFunctions.forEach(function(requestedFunction, i) {
	process.stderr.write(""+(i+1));
	process.stderr.write(".\t");
	process.stderr.write(requestedFunction.functionName);
	process.stderr.write("\n");
});

if(program.output) {
	process.stderr.write("\n");
	process.stderr.write("File written to: " + program.output + ".");
	process.stderr.write("\n");
}

process.stderr.write("\nEnjoy and happy cross-browser scripting!");
process.stderr.write("\n\n========================================================\n\n");

function list(val) {
	return val.split(',');
}

function setupRequestedFunctions() {
	// parse the args to get a map of functions that we need to output
	program.args.forEach(function(arg){
		// each argument can specify the renditions that are required
		// by separating them with colons
		arg = arg.split(":");

		// first part is the function name
		var functionName = arg[0];

		// second part is the renditionId (1 or 2 or 3 etc)
		var renditionId = arg[1];

		if(!renditionId) {
			throw new Error("You must specify a particular rendition e.g. "+functionName+":1");
		}

		if(arg.length > 2) {
			throw new Error("Only one rendition allowed");
		}

		requestedFunctions.push({
			functionName: functionName,
			renditionId: parseInt(renditionId, 10)
		});

		if(functionSet.getFunctionByName(functionName) === null) {
			throw new Error("Could not find function: " + functionName);
		}
	});
}<|endoftext|>


<commit_before>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var id2name = require('./axis_ids').id2name;
var scaleZoom = require('./scale_zoom');

var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL;

var FROM_BL = require('../../constants/alignment').FROM_BL;

exports.enforce = function enforceAxisConstraints(gd) {
    var fullLayout = gd._fullLayout;
    var constraintGroups = fullLayout._axisConstraintGroups;

    var i, j, axisID, ax, normScale, mode, factor;

    for(i = 0; i < constraintGroups.length; i++) {
        var group = constraintGroups[i];
        var axisIDs = Object.keys(group);

        var minScale = Infinity;
        var maxScale = 0;
        // mostly matchScale will be the same as minScale
        // ie we expand axis ranges to encompass *everything*
        // that's currently in any of their ranges, but during
        // autorange of a subset of axes we will ignore other
        // axes for this purpose.
        var matchScale = Infinity;
        var normScales = {};
        var axes = {};

        // find the (normalized) scale of each axis in the group
        for(j = 0; j < axisIDs.length; j++) {
            axisID = axisIDs[j];
            axes[axisID] = ax = fullLayout[id2name(axisID)];

            if(!ax._inputDomain) ax._inputDomain = ax.domain.slice();
            if(!ax._inputRange) ax._inputRange = ax.range.slice();

            // set axis scale here so we can use _m rather than
            // having to calculate it from length and range
            ax.setScale();

            // abs: inverted scales still satisfy the constraint
            normScales[axisID] = normScale = Math.abs(ax._m) / group[axisID];
            minScale = Math.min(minScale, normScale);
            if(ax._constraintShrinkable) {
                // this has served its purpose, so remove it
                delete ax._constraintShrinkable;
            }
            else {
                matchScale = Math.min(matchScale, normScale);
            }
            maxScale = Math.max(maxScale, normScale);
        }

        // Do we have a constraint mismatch? Give a small buffer for rounding errors
        if(minScale > ALMOST_EQUAL * maxScale) continue;

        // now increase any ranges we need to until all normalized scales are equal
        for(j = 0; j < axisIDs.length; j++) {
            axisID = axisIDs[j];
            normScale = normScales[axisID];
            ax = axes[axisID];
            mode = ax.constrain;

            // even if the scale didn't change, if we're shrinking domain
            // we need to recalculate in case `constraintoward` changed
            if(normScale !== matchScale || mode === 'domain') {
                factor = normScale / matchScale;

                if(mode === 'range') {
                    scaleZoom(ax, factor);
                }
                else {
                    // mode === 'domain'

                    var inputDomain = ax._inputDomain;
                    var domainShrunk = (ax.domain[1] - ax.domain[0]) /
                        (inputDomain[1] - inputDomain[0]);
                    var rangeShrunk = (ax.r2l(ax.range[1]) - ax.r2l(ax.range[0])) /
                        (ax.r2l(ax._inputRange[1]) - ax.r2l(ax._inputRange[0]));

                    factor /= domainShrunk;

                    if(factor * rangeShrunk < 1) {
                        // we've asked to magnify the axis more than we can just by
                        // enlarging the domain - so we need to constrict range
                        ax.domain = ax._input.domain = inputDomain.slice();
                        scaleZoom(ax, factor);
                        continue;
                    }

                    if(rangeShrunk < 1) {
                        // the range has previously been constricted by ^^, but we've
                        // switched to the domain-constricted regime, so reset range
                        ax.range = ax._input.range = ax._inputRange.slice();
                        factor *= rangeShrunk;
                    }

                    // TODO
                    if(ax.autorange) {
                        /*
                         * range & factor may need to change because range was
                         * calculated for the larger scaling, so some pixel
                         * paddings may get cut off when we reduce the domain.
                         *
                         * This is easier than the regular autorange calculation
                         * because we already know the scaling `m`, but we still
                         * need to cut out impossible constraints (like
                         * annotations with super-long arrows). That's what
                         * outerMin/Max are for - if the expansion was going to
                         * go beyond the original domain, it must be impossible
                         */
                        var rangeMin = Math.min(ax.range[0], ax.range[1]);
                        var rangeMax = Math.max(ax.range[0], ax.range[1]);
                        var rangeCenter = (rangeMin + rangeMax) / 2;
                        var halfRange = rangeMax - rangeCenter;
                        var outerMin = rangeCenter - halfRange * factor;
                        var outerMax = rangeCenter + halfRange * factor;

                        updateDomain(ax, factor);
                        ax.setScale();
                        var m = Math.abs(ax._m);
                        var newVal;
                        var k;

                        for(k = 0; k < ax._min.length; k++) {
                            newVal = ax._min[i].val - ax._min[i].pad / m;
                            if(newVal > outerMin && newVal < rangeMin) {
                                rangeMin = newVal;
                            }
                        }

                        for(k = 0; k < ax._max.length; k++) {
                            newVal = ax._max[i].val + ax._max[i].pad / m;
                            if(newVal < outerMax && newVal > rangeMax) {
                                rangeMax = newVal;
                            }
                        }

                        ax.range = ax._input.range = (ax.range[0] < ax.range[1]) ?
                            [rangeMin, rangeMax] : [rangeMax, rangeMin];

                        /*
                         * In principle this new range can be shifted vs. what
                         * you saw at the end of a zoom operation, like if you
                         * have a big bubble on one side and a small bubble on
                         * the other.
                         * To fix this we'd have to be doing this calculation
                         * continuously during the zoom, but it's enough of an
                         * edge case and a subtle enough effect that I'm going
                         * to ignore it for now.
                         */
                        var domainExpand = (rangeMax - rangeMin) / (2 * halfRange);
                        factor /= domainExpand;
                    }

                    updateDomain(ax, factor);
                }
            }
        }
    }
};

// For use before autoranging, check if this axis was previously constrained
// by domain but no longer is
exports.clean = function cleanConstraints(gd, ax) {
    if(ax._inputDomain) {
        var isConstrained = false;
        var axId = ax._id;
        var constraintGroups = gd._fullLayout._axisConstraintGroups;
        for(var j = 0; j < constraintGroups.length; j++) {
            if(constraintGroups[j][axId]) {
                isConstrained = true;
                break;
            }
        }
        if(!isConstrained || ax.constrain !== 'domain') {
            ax._input.domain = ax.domain = ax._inputDomain;
            delete ax._inputDomain;
        }
    }
};

function updateDomain(ax, factor) {
    var inputDomain = ax._inputDomain;
    var centerFraction = FROM_BL[ax.constraintoward];
    var center = inputDomain[0] + (inputDomain[1] - inputDomain[0]) * centerFraction;

    ax.domain = ax._input.domain = [
        center + (inputDomain[0] - center) / factor,
        center + (inputDomain[1] - center) / factor
    ];
}
<commit_msg>update constraints.js to handle some more editing edge cases<commit_after>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var id2name = require('./axis_ids').id2name;
var scaleZoom = require('./scale_zoom');

var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL;

var FROM_BL = require('../../constants/alignment').FROM_BL;

exports.enforce = function enforceAxisConstraints(gd) {
    var fullLayout = gd._fullLayout;
    var constraintGroups = fullLayout._axisConstraintGroups;

    var i, j, axisID, ax, normScale, mode, factor;

    for(i = 0; i < constraintGroups.length; i++) {
        var group = constraintGroups[i];
        var axisIDs = Object.keys(group);

        var minScale = Infinity;
        var maxScale = 0;
        // mostly matchScale will be the same as minScale
        // ie we expand axis ranges to encompass *everything*
        // that's currently in any of their ranges, but during
        // autorange of a subset of axes we will ignore other
        // axes for this purpose.
        var matchScale = Infinity;
        var normScales = {};
        var axes = {};
        var hasAnyDomainConstraint = false;

        // find the (normalized) scale of each axis in the group
        for(j = 0; j < axisIDs.length; j++) {
            axisID = axisIDs[j];
            axes[axisID] = ax = fullLayout[id2name(axisID)];

            if(ax._inputDomain) ax.domain = ax._inputDomain.slice();
            else ax._inputDomain = ax.domain.slice();

            if(!ax._inputRange) ax._inputRange = ax.range.slice();

            // set axis scale here so we can use _m rather than
            // having to calculate it from length and range
            ax.setScale();

            // abs: inverted scales still satisfy the constraint
            normScales[axisID] = normScale = Math.abs(ax._m) / group[axisID];
            minScale = Math.min(minScale, normScale);
            if(ax.constrain === 'domain' || !ax._constraintShrinkable) {
                matchScale = Math.min(matchScale, normScale);
            }

            // this has served its purpose, so remove it
            delete ax._constraintShrinkable;
            maxScale = Math.max(maxScale, normScale);

            if(ax.constrain === 'domain') hasAnyDomainConstraint = true;
        }

        // Do we have a constraint mismatch? Give a small buffer for rounding errors
        if(minScale > ALMOST_EQUAL * maxScale && !hasAnyDomainConstraint) continue;

        // now increase any ranges we need to until all normalized scales are equal
        for(j = 0; j < axisIDs.length; j++) {
            axisID = axisIDs[j];
            normScale = normScales[axisID];
            ax = axes[axisID];
            mode = ax.constrain;

            // even if the scale didn't change, if we're shrinking domain
            // we need to recalculate in case `constraintoward` changed
            if(normScale !== matchScale || mode === 'domain') {
                factor = normScale / matchScale;

                if(mode === 'range') {
                    scaleZoom(ax, factor);
                }
                else {
                    // mode === 'domain'

                    var inputDomain = ax._inputDomain;
                    var domainShrunk = (ax.domain[1] - ax.domain[0]) /
                        (inputDomain[1] - inputDomain[0]);
                    var rangeShrunk = (ax.r2l(ax.range[1]) - ax.r2l(ax.range[0])) /
                        (ax.r2l(ax._inputRange[1]) - ax.r2l(ax._inputRange[0]));

                    factor /= domainShrunk;

                    if(factor * rangeShrunk < 1) {
                        // we've asked to magnify the axis more than we can just by
                        // enlarging the domain - so we need to constrict range
                        ax.domain = ax._input.domain = inputDomain.slice();
                        scaleZoom(ax, factor);
                        continue;
                    }

                    if(rangeShrunk < 1) {
                        // the range has previously been constricted by ^^, but we've
                        // switched to the domain-constricted regime, so reset range
                        ax.range = ax._input.range = ax._inputRange.slice();
                        factor *= rangeShrunk;
                    }

                    if(ax.autorange && ax._min.length && ax._max.length) {
                        /*
                         * range & factor may need to change because range was
                         * calculated for the larger scaling, so some pixel
                         * paddings may get cut off when we reduce the domain.
                         *
                         * This is easier than the regular autorange calculation
                         * because we already know the scaling `m`, but we still
                         * need to cut out impossible constraints (like
                         * annotations with super-long arrows). That's what
                         * outerMin/Max are for - if the expansion was going to
                         * go beyond the original domain, it must be impossible
                         */
                        var rl0 = ax.r2l(ax.range[0]);
                        var rl1 = ax.r2l(ax.range[1]);
                        var rangeCenter = (rl0 + rl1) / 2;
                        var rangeMin = rangeCenter;
                        var rangeMax = rangeCenter;
                        var halfRange = Math.abs(rl1 - rangeCenter);
                        // extra tiny bit for rounding errors, in case we actually
                        // *are* expanding to the full domain
                        var outerMin = rangeCenter - halfRange * factor * 1.0001;
                        var outerMax = rangeCenter + halfRange * factor * 1.0001;

                        updateDomain(ax, factor);
                        ax.setScale();
                        var m = Math.abs(ax._m);
                        var newVal;
                        var k;

                        for(k = 0; k < ax._min.length; k++) {
                            newVal = ax._min[k].val - ax._min[k].pad / m;
                            if(newVal > outerMin && newVal < rangeMin) {
                                rangeMin = newVal;
                            }
                        }

                        for(k = 0; k < ax._max.length; k++) {
                            newVal = ax._max[k].val + ax._max[k].pad / m;
                            if(newVal < outerMax && newVal > rangeMax) {
                                rangeMax = newVal;
                            }
                        }

                        var domainExpand = (rangeMax - rangeMin) / (2 * halfRange);
                        factor /= domainExpand;

                        rangeMin = ax.l2r(rangeMin);
                        rangeMax = ax.l2r(rangeMax);
                        ax.range = ax._input.range = (rl0 < rl1) ?
                            [rangeMin, rangeMax] : [rangeMax, rangeMin];
                    }

                    updateDomain(ax, factor);
                }
            }
        }
    }
};

// For use before autoranging, check if this axis was previously constrained
// by domain but no longer is
exports.clean = function cleanConstraints(gd, ax) {
    if(ax._inputDomain) {
        var isConstrained = false;
        var axId = ax._id;
        var constraintGroups = gd._fullLayout._axisConstraintGroups;
        for(var j = 0; j < constraintGroups.length; j++) {
            if(constraintGroups[j][axId]) {
                isConstrained = true;
                break;
            }
        }
        if(!isConstrained || ax.constrain !== 'domain') {
            ax._input.domain = ax.domain = ax._inputDomain;
            delete ax._inputDomain;
        }
    }
};

function updateDomain(ax, factor) {
    var inputDomain = ax._inputDomain;
    var centerFraction = FROM_BL[ax.constraintoward];
    var center = inputDomain[0] + (inputDomain[1] - inputDomain[0]) * centerFraction;

    ax.domain = ax._input.domain = [
        center + (inputDomain[0] - center) / factor,
        center + (inputDomain[1] - center) / factor
    ];
}
<|endoftext|>


<commit_before>var dali = function() {

  var slide_increment = 75;
  var slide_counter = 0;
  var post_fields = {};

  var save_callback = function(data) {
    var status = 'success';
    if(data != "Success") {
      status = 'error';
    }
    dali.show_status(data, status);
  };

  return {

    show_lightbox: function() {
      var ctrl = $('

')
        .text('close')
        .click(dali.hide_lightbox);
      $("

")
        .appendTo(document.body)
        .append(ctrl)
        .hide()
        .fadeIn('slow');
    },

    hide_lightbox : function() {
      $("#lightbox").fadeOut('slow', function(){
        $(this).remove();
      });
    },

    update_post : function(value, settings) {
      post_fields[settings.id] = value;
      return value;
    },

    save_post : function(url) {
      if(dali.object_size(post_fields) > 0) {
        $.post(url, post_fields, save_callback, 'text');
        post_fields = {};
      } else {
        dali.show_status('Nothing to save', 'info');
      }
    },

    slide_right : function(div) {
       if(slide_counter > 0) {
          slide_counter--;
          $(div).animate({right: 2 * slide_counter * slide_increment}, 1000);
       }
    },

    slide_left : function(div) {
       if(slide_counter < 8) {
          slide_counter++;
          $(div).animate({right: 2 * slide_counter * slide_increment}, 1000);
       }
    },

    show_status : function(message, type) {
      var statusbar = $("

")
        .addClass(type)
        .text(message)
        .appendTo(document.body)
        .hide()
        .fadeIn('slow');

      setTimeout(function() {
        statusbar.fadeOut('slow', function() { statusbar.remove(); });
      }, 10000);
    },

    object_size : function(obj) {
      var count = 0;
      for(var p in obj) {
        if(obj.hasOwnProperty(p)) {
          count++;
        }
      }
      return count;
    }
  };
}();
<commit_msg>Replaced $ with jQuery<commit_after>var dali = function() {

  var slide_increment = 75;
  var slide_counter = 0;
  var post_fields = {};

  var save_callback = function(data) {
    var status = 'success';
    if(data != "Success") {
      status = 'error';
    }
    dali.show_status(data, status);
  };

  return {

    show_lightbox: function() {
      var ctrl = jQuery('

')
        .text('close')
        .click(dali.hide_lightbox);
      jQuery("

")
        .appendTo(document.body)
        .append(ctrl)
        .hide()
        .fadeIn('slow');
    },

    hide_lightbox : function() {
      jQuery("#lightbox").fadeOut('slow', function(){
        jQuery(this).remove();
      });
    },

    update_post : function(value, settings) {
      post_fields[settings.id] = value;
      return value;
    },

    save_post : function(url) {
      if(dali.object_size(post_fields) > 0) {
        jQuery.post(url, post_fields, save_callback, 'text');
        post_fields = {};
      } else {
        dali.show_status('Nothing to save', 'info');
      }
    },

    slide_right : function(div) {
       if(slide_counter > 0) {
          slide_counter--;
          jQuery(div).animate({right: 2 * slide_counter * slide_increment}, 1000);
       }
    },

    slide_left : function(div) {
       if(slide_counter < 8) {
          slide_counter++;
          jQuery(div).animate({right: 2 * slide_counter * slide_increment}, 1000);
       }
    },

    show_status : function(message, type) {
      var statusbar = jQuery("

")
        .addClass(type)
        .text(message)
        .appendTo(document.body)
        .hide()
        .fadeIn('slow');

      setTimeout(function() {
        statusbar.fadeOut('slow', function() { statusbar.remove(); });
      }, 10000);
    },

    object_size : function(obj) {
      var count = 0;
      for(var p in obj) {
        if(obj.hasOwnProperty(p)) {
          count++;
        }
      }
      return count;
    }
  };
}();
<|endoftext|>