<commit_before>'use babel'

import {CompositeDisposable} from 'atom'
import {Range} from 'atom'
import {spawnSync} from 'child_process'

import EditScope from './edit_scope'
import PipeView from './pipe_view'

export default class PipeUtils {
  constructor () {
    this.subscriptions = null
  }

  activate () {
    this.subscriptions = new CompositeDisposable
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'pipe-utils:show': () => this.showPanel(),
      'pipe-utils:toggle': () => this.togglePanel()
    }))
  }

  deactivate () {
    this.pipePanel.destroy()
    this.pipePanel = null

    this.pipeView.destroy()
    this.pipeView = null

    this.subscriptions.dispose()
    this.subscriptions = null
  }

  showPanel () {
    this.createViews()
    this.pipePanel.show()
    this.pipeView.focusPipeEditor()
  }

  togglePanel () {
    this.createViews()
    if (this.pipePanel.isVisible()) {
      this.pipePanel.hide()
    } else {
      this.pipePanel.show()
      this.pipeView.focusPipeEditor()
    }
  }

  focusActiveTextEditor () {
    editor = atom.workspace.getActiveTextEditor()
    if (!editor) {
      return
    }
    view = atom.views.getView(editor)
    if (!view) {
      return
    }
    view.focus()
  }

  pipeSelection (command) {
    editor = atom.workspace.getActiveTextEditor()
    if (!editor) {
      return
    }
    view = atom.views.getView(editor)
    if (!view) {
      return
    }

    scope = new EditScope(() => {
      editor.groupChangesSinceCheckpoint()
      view.focus()
    })

    ranges = editor.getSelectedBufferRanges()
    scope.add(ranges.length)
    editor.createCheckpoint()

    properties = { reversed: true, invalidate: 'never' }
    ranges.forEach(range => {
      marker = editor.markBufferRange(range, properties)
      this.pipeRange(command, marker, editor, scope)
    })
  }

  pipeRange (command, marker, editor, scope) {
    selection = editor.getTextInBufferRange(marker.getBufferRange())
    output = spawnSync(
      process.env.SHELL,
      ["-l", "-c", command],
      { input:selection, encoding: 'utf8' })

    if (output.stdout) {
      editor.setTextInBufferRange(marker.getBufferRange(), output.stdout)
    }

    if (output.stderr) {
      atom.notifications.addWarning(output.stderr)
    }
    scope.done()
  }

  createViews () {
    if (this.pipeView) {
      return
    }
    this.pipeView = new PipeView(this)
    this.pipePanel = atom.workspace.addBottomPanel({
      item: this.pipeView, visible: false, className: 'tool-panel panel-bottom'})
    this.pipeView.setPanel(this.pipePanel)
  }

  // Delegate method. Called when the pipeView confirms the command.
  confirm(command) {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
    this.pipeSelection(command)
  }

  // Delegate method. Called when the window closes.
  close() {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
  }

  // Delegate method. Called when the pipeView cancels the interaction.
  cancel() {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
  }
}
<commit_msg>Set cwd to path of file open in the editor.<commit_after>'use babel'

import {CompositeDisposable} from 'atom'
import {Range} from 'atom'
import {spawnSync} from 'child_process'

import EditScope from './edit_scope'
import PipeView from './pipe_view'

export default class PipeUtils {
  constructor () {
    this.subscriptions = null
  }

  activate () {
    this.subscriptions = new CompositeDisposable
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'pipe-utils:show': () => this.showPanel(),
      'pipe-utils:toggle': () => this.togglePanel()
    }))
  }

  deactivate () {
    this.pipePanel.destroy()
    this.pipePanel = null

    this.pipeView.destroy()
    this.pipeView = null

    this.subscriptions.dispose()
    this.subscriptions = null
  }

  showPanel () {
    this.createViews()
    this.pipePanel.show()
    this.pipeView.focusPipeEditor()
  }

  togglePanel () {
    this.createViews()
    if (this.pipePanel.isVisible()) {
      this.pipePanel.hide()
    } else {
      this.pipePanel.show()
      this.pipeView.focusPipeEditor()
    }
  }

  focusActiveTextEditor () {
    editor = atom.workspace.getActiveTextEditor()
    if (!editor) {
      return
    }
    view = atom.views.getView(editor)
    if (!view) {
      return
    }
    view.focus()
  }

  pipeSelection (command) {
    editor = atom.workspace.getActiveTextEditor()
    if (!editor) {
      return
    }
    view = atom.views.getView(editor)
    if (!view) {
      return
    }

    scope = new EditScope(() => {
      editor.groupChangesSinceCheckpoint()
      view.focus()
    })

    ranges = editor.getSelectedBufferRanges()
    scope.add(ranges.length)
    editor.createCheckpoint()

    properties = { reversed: true, invalidate: 'never' }
    ranges.forEach(range => {
      marker = editor.markBufferRange(range, properties)
      this.pipeRange(command, marker, editor, scope)
    })
  }

  pipeRange (command, marker, editor, scope) {
    selection = editor.getTextInBufferRange(marker.getBufferRange())
    output = spawnSync(
      process.env.SHELL,
      ["-l", "-c", command],
      { input:selection, encoding:'utf8', cwd:editor.getDirectoryPath() })

    if (output.stdout) {
      editor.setTextInBufferRange(marker.getBufferRange(), output.stdout)
    }

    if (output.stderr) {
      atom.notifications.addWarning(output.stderr)
    }
    scope.done()
  }

  createViews () {
    if (this.pipeView) {
      return
    }
    this.pipeView = new PipeView(this)
    this.pipePanel = atom.workspace.addBottomPanel({
      item: this.pipeView, visible: false, className: 'tool-panel panel-bottom'})
    this.pipeView.setPanel(this.pipePanel)
  }

  // Delegate method. Called when the pipeView confirms the command.
  confirm(command) {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
    this.pipeSelection(command)
  }

  // Delegate method. Called when the window closes.
  close() {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
  }

  // Delegate method. Called when the pipeView cancels the interaction.
  cancel() {
    this.pipePanel.hide()
    this.focusActiveTextEditor()
  }
}
<|endoftext|>


<commit_before>const fetch = require('sync-fetch')
const path = require('path')
const {
  getAuthHeaders
} = require('./pactHelper.js')

/**
 *
 * @param {string} userId
 * @param {string} element
 */
const createDavPath = function (userId, element) {
  return `/remote.php/dav/files/${userId}/${element}`
}

/**
 * returns the full and sanitized URL of the dav resource
 * @param {string} userId
 * @param {string} resource
 * @returns {string}
 */
const createFullDavUrl = function (userId, resource) {
  return (process.env.PROVIDER_BASE_URL + createDavPath(userId, resource))
    .replace(/([^:])\/{2,}/g, '$1/')
}

/**
 * Create a folder using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} folderName
 * @returns {[]} all fetch results
 */
const createFolderRecrusive = function (user, password, folderName) {
  const results = []
  const folders = folderName.split(path.sep)
  for (let i = 0; i < folders.length; i++) {
    let recrusivePath = ''
    for (let j = 0; j <= i; j++) {
      recrusivePath += path.sep + folders[j]
    }
    results[i] = fetch(createFullDavUrl(user, recrusivePath), {
      method: 'MKCOL',
      headers: { authorization: getAuthHeaders(user, password) }
    })
  }
  return results
}

/**
 * Create a file using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} fileName
 * @param {string} contents
 * @returns {*} result of the fetch request
 */
const createFile = function (user, password, fileName, contents = '') {
  return fetch(createFullDavUrl(user, fileName), {
    method: 'PUT',
    headers: { authorization: getAuthHeaders(user, password) },
    body: contents
  })
}

/**
 * Delete a file or folder using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} itemName
 * @returns {*} result of the fetch request
 */
const deleteItem = function (user, password, itemName) {
  return fetch(createFullDavUrl(user, itemName), {
    method: 'DELETE',
    headers: { authorization: getAuthHeaders(user, password) }
  })
}

module.exports = {
  createFolderRecrusive,
  createFile,
  deleteItem
}
<commit_msg>strip leading and trailing slash<commit_after>const fetch = require('sync-fetch')
const path = require('path')
const {
  getAuthHeaders
} = require('./pactHelper.js')

/**
 *
 * @param {string} userId
 * @param {string} element
 */
const createDavPath = function (userId, element) {
  return `/remote.php/dav/files/${userId}/${element}`
}

/**
 * returns the full and sanitized URL of the dav resource
 * @param {string} userId
 * @param {string} resource
 * @returns {string}
 */
const createFullDavUrl = function (userId, resource) {
  return (process.env.PROVIDER_BASE_URL + createDavPath(userId, resource))
    .replace(/([^:])\/{2,}/g, '$1/')
}

/**
 * Create a folder using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} folderName
 * @returns {[]} all fetch results
 */
const createFolderRecrusive = function (user, password, folderName) {
  const results = []
  folderName = folderName.replace(/\/$/, '')
  folderName = folderName.replace(/^\//, '')
  const folders = folderName.split(path.sep)
  for (let i = 0; i < folders.length; i++) {
    let recrusivePath = ''
    for (let j = 0; j <= i; j++) {
      recrusivePath += path.sep + folders[j]
    }
    results[i] = fetch(createFullDavUrl(user, recrusivePath), {
      method: 'MKCOL',
      headers: { authorization: getAuthHeaders(user, password) }
    })
  }
  return results
}

/**
 * Create a file using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} fileName
 * @param {string} contents
 * @returns {*} result of the fetch request
 */
const createFile = function (user, password, fileName, contents = '') {
  return fetch(createFullDavUrl(user, fileName), {
    method: 'PUT',
    headers: { authorization: getAuthHeaders(user, password) },
    body: contents
  })
}

/**
 * Delete a file or folder using webDAV api.
 *
 * @param {string} user
 * @param {string} password
 * @param {string} itemName
 * @returns {*} result of the fetch request
 */
const deleteItem = function (user, password, itemName) {
  return fetch(createFullDavUrl(user, itemName), {
    method: 'DELETE',
    headers: { authorization: getAuthHeaders(user, password) }
  })
}

module.exports = {
  createFolderRecrusive,
  createFile,
  deleteItem
}
<|endoftext|>


<commit_before>/*
	neat 8.2 - a lightweight ui-handler for jquery
	Copyright 2014 medienreaktor GmbH
*/

(function ($) {

	$.fn.toggleAttr = function(attr, attr_con) {

		var el = $(this);

		if(el.attr(attr)) {
			el.removeAttr(attr);
		} else {
			el.attr(attr, attr_con);
		}

	}

	$.fn.accordion = function(raw_speed, all) {

		var root = $(this);
		var speed = raw_speed;

		$(this).find('dd').css('display', 'none');

		$(this).find('dt').click(function(){

			var open_items = $(this).next().hasClass('open');
			var next = $(this).next();
			var opened = $(this).closest('dl').find('.open');

			if(open_items != true){
				opened.slideUp(speed);
				opened.removeAttr('class');
				$(this).closest('dl').find('.active').removeClass('active');
			};

			next.slideToggle(speed);
			next.toggleClass('open');
			$(this).toggleClass('active');

		});

		return {

			multi: function(sort) {

				if(sort == 'open' && root.hasClass('all-open') == false) {
					root.find('dd').not(".open").slideDown(speed).addClass('open');
					root.find('dt').not(".active").addClass('active');

					root.removeClass('all-closed');
					root.addClass('all-open');
				}

				if(sort == 'close' && root.hasClass('all-closed') == false) {
					root.find('.open').slideUp(speed).removeClass('open');
					root.find('.active').removeClass('active');

					root.removeClass('all-open');
					root.addClass('all-closed');
				}

				if(sort == 'toggle') {
					root.find('dd').slideToggle(speed).toggleClass('open');
					root.find('dt').toggleClass('active');

					root.removeClass('all-open all-closed');
				}

			}

		}

	}

	$.fn.tabs = function() {

		$(this).find('section article').not('.active').css('display', 'none');

		$(this).find('nav ul li a').click(function(event){

			var curr = $(this).closest('li').hasClass('active');
			var id = $(this).attr('href').split('#')[1];
			var tabs = $(this).closest('div[class*="tabs"]');

			if(curr != true){
				$(this).closest('ul').find('.active').removeClass('active');
				$(this).closest('li').addClass('active');

				tabs.find('article.active').hide().removeClass('active');
				tabs.find('#' + id).show().addClass('active');
			};

			event.preventDefault();
		});

	}

	$.fn.tooltip = function(speed) {

		if(! speed) {
			var speed = 300;
		};

		$(this).hover(function(){

			var tooltip_content = $(this).attr('title');
			var position = $(this).position();

			$(this).data('tooltip', tooltip_content).removeAttr('title');

			var styling =	'display: none; ' +
					'position: absolute; ' +
					'left: ' + position.left + 'px; ' +
					'top: ' + (position.top + $(this).outerHeight(true)) + 'px;';

			$('body').append('
' + tooltip_content + '
');
			$('div[role="tooltip"]').fadeIn(speed);

		}, function(){

			$(this).attr('title', $(this).data('tooltip'));
			$('div[role="tooltip"]').fadeOut(speed, function(){
				$(this).remove();
			});

		});

	}

}(jQuery));
<commit_msg>Version changed<commit_after>/*
	neat 8.3 - a lightweight ui-handler for jquery
	Copyright 2014 medienreaktor GmbH
*/

(function ($) {

	$.fn.toggleAttr = function(attr, attr_con) {

		var el = $(this);

		if(el.attr(attr)) {
			el.removeAttr(attr);
		} else {
			el.attr(attr, attr_con);
		}

	}

	$.fn.accordion = function(raw_speed, all) {

		var root = $(this);
		var speed = raw_speed;

		$(this).find('dd').css('display', 'none');

		$(this).find('dt').click(function(){

			var open_items = $(this).next().hasClass('open');
			var next = $(this).next();
			var opened = $(this).closest('dl').find('.open');

			if(open_items != true){
				opened.slideUp(speed);
				opened.removeAttr('class');
				$(this).closest('dl').find('.active').removeClass('active');
			};

			next.slideToggle(speed);
			next.toggleClass('open');
			$(this).toggleClass('active');

		});

		return {

			multi: function(sort) {

				if(sort == 'open' && root.hasClass('all-open') == false) {
					root.find('dd').not(".open").slideDown(speed).addClass('open');
					root.find('dt').not(".active").addClass('active');

					root.removeClass('all-closed');
					root.addClass('all-open');
				}

				if(sort == 'close' && root.hasClass('all-closed') == false) {
					root.find('.open').slideUp(speed).removeClass('open');
					root.find('.active').removeClass('active');

					root.removeClass('all-open');
					root.addClass('all-closed');
				}

				if(sort == 'toggle') {
					root.find('dd').slideToggle(speed).toggleClass('open');
					root.find('dt').toggleClass('active');

					root.removeClass('all-open all-closed');
				}

			}

		}

	}

	$.fn.tabs = function() {

		$(this).find('section article').not('.active').css('display', 'none');

		$(this).find('nav ul li a').click(function(event){

			var curr = $(this).closest('li').hasClass('active');
			var id = $(this).attr('href').split('#')[1];
			var tabs = $(this).closest('div[class*="tabs"]');

			if(curr != true){
				$(this).closest('ul').find('.active').removeClass('active');
				$(this).closest('li').addClass('active');

				tabs.find('article.active').hide().removeClass('active');
				tabs.find('#' + id).show().addClass('active');
			};

			event.preventDefault();
		});

	}

	$.fn.tooltip = function(speed) {

		if(! speed) {
			var speed = 300;
		};

		$(this).hover(function(){

			var tooltip_content = $(this).attr('title');
			var position = $(this).position();

			$(this).data('tooltip', tooltip_content).removeAttr('title');

			var styling =	'display: none; ' +
					'position: absolute; ' +
					'left: ' + position.left + 'px; ' +
					'top: ' + (position.top + $(this).outerHeight(true)) + 'px;';

			$('body').append('
' + tooltip_content + '
');
			$('div[role="tooltip"]').fadeIn(speed);

		}, function(){

			$(this).attr('title', $(this).data('tooltip'));
			$('div[role="tooltip"]').fadeOut(speed, function(){
				$(this).remove();
			});

		});

	}

}(jQuery));
<|endoftext|>


<commit_before>const {
  isString,
  isDefinedOrThrow,
  isStringOrObjectOrThrow
} = require("../../utils/fun.js");

function JSONParser() {

}

const invalidPayloadTypeError =
  new Error("invalid payload type, allowed are: string or object");

const nullOrIndefinedPayload =
  new Error("null or undefined payload");

// Function
const asJSON = (v) => (isString(v) ? JSON.parse(v) : v);

/**
 * Level 0 of validation: is that string? is that JSON?
 */
function validateAndParse(payload) {

  var json =
    Array.of(payload)
      .filter((p) => isDefinedOrThrow(p, nullOrIndefinedPayload))
      .filter((p) => isStringOrObjectOrThrow(p, invalidPayloadTypeError))
      .map(asJSON)
      .shift();

  return json;
}

/*
 * Level 1 of validation: is that follow a spec?
 */
function validateSpec(payload, spec) {

  // is that follow the spec?
  spec.check(payload);

  return payload;
}

JSONParser.prototype.parse = function(payload) {

  //is that string? is that JSON?
  var valid = validateAndParse(payload);

  return valid;
}

module.exports = JSONParser;
<commit_msg>Code style: missing semicolon<commit_after>const {
  isString,
  isDefinedOrThrow,
  isStringOrObjectOrThrow
} = require("../../utils/fun.js");

function JSONParser() {

}

const invalidPayloadTypeError =
  new Error("invalid payload type, allowed are: string or object");

const nullOrIndefinedPayload =
  new Error("null or undefined payload");

// Function
const asJSON = (v) => (isString(v) ? JSON.parse(v) : v);

/**
 * Level 0 of validation: is that string? is that JSON?
 */
function validateAndParse(payload) {

  var json =
    Array.of(payload)
      .filter((p) => isDefinedOrThrow(p, nullOrIndefinedPayload))
      .filter((p) => isStringOrObjectOrThrow(p, invalidPayloadTypeError))
      .map(asJSON)
      .shift();

  return json;
}

/*
 * Level 1 of validation: is that follow a spec?
 */
function validateSpec(payload, spec) {

  // is that follow the spec?
  spec.check(payload);

  return payload;
}

JSONParser.prototype.parse = function(payload) {

  //is that string? is that JSON?
  var valid = validateAndParse(payload);

  return valid;
};

module.exports = JSONParser;
<|endoftext|>


<commit_before>/*
 * jQuery hashchange event - v1.3 - 7/21/2010
 * http://benalman.com/projects/jquery-hashchange-plugin/
 *
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */
(function($,e,b){
  var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);
  function a(j){
    j=j||location.href;
    return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")
  }
  $.fn[c]=function(j){
    return j?this.bind(c,j):this.trigger(c)
  };

  $.fn[c].delay=50;
  g[c]=$.extend(g[c],{
    setup:function(){
      if(d){
        return false
      }
      $(f.start)
    },
    teardown:function(){
      if(d){
        return false
      }
      $(f.stop)
    }
  });
  f=(function(){
    var j={},p,m=a(),k=function(q){
      return q
    },l=k,o=k;
    j.start=function(){
      p||n()
    };

    j.stop=function(){
      p&&clearTimeout(p);
      p=b
    };

    function n(){
      var r=a(),q=o(m);
      if(r!==m){
        l(m=r,q);
        $(e).trigger(c)
      }else{
        if(q!==m){
          location.href=location.href.replace(/#.*/,"")+q
        }
      }
      p=setTimeout(n,$.fn[c].delay)
    }
    $.browser.msie&&!d&&(function(){
      var q,r;
      j.start=function(){
        if(!q){
          r=$.fn[c].src;
          r=r&&r+a();
          q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){
            r||l(a());
            n()
            }).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;
          h.onpropertychange=function(){
            try{
              if(event.propertyName==="title"){
                q.document.title=h.title
              }
            }catch(s){}
          }
        }
      };

      j.stop=k;
      o=function(){
        return a(q.location.href)
      };

      l=function(v,s){
        var u=q.document,t=$.fn[c].domain;
        if(v!==s){
          u.title=h.title;
          u.open();
          t&&u.write('<script>document.domain="'+t+'"<\/script>');
          u.close();
          q.location.hash=v
        }
      }
    })();
    return j
  })()
})(jQuery,this);

/* jquery.pjax.js with hash navigation fallback + form pjax + get forms parameters in address
 * https://github.com/imsamurai/jquery-pjax
*/

/* jquery.pjax.js with hash-navigation fallback
 * copyright andrew magalich
 * https://github.com/ckald/jquery-pjax
*/

// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/jquery-pjax

(function($){
  if (!$.hash) $.hash = '#!/';
  if (!$.siteurl) $.siteurl = document.location.protocol+'//'+document.location.host; // your site url
  if (!$.container) $.container = '#content'; // container SELECTOR to use for hash navigation

  // When called on a link, fetches the href with ajax into the
  // container specified as the first parameter or with the data-pjax
  // attribute on the link itself.
  //
  // Tries to make sure the back button and ctrl+click work the way
  // you'd expect.
  //
  // Accepts a jQuery ajax options object that may include these
  // pjax specific options:
  //
  // container - Where to stick the response body. Usually a String selector.
  //             $(container).html(xhr.responseBody)
  //      push - Whether to pushState the URL. Defaults to true (of course).
  //   replace - Want to use replaceState instead? That's cool.
  //
  // For convenience the first parameter can be either the container or
  // the options object.
  //
  // Returns the jQuery object
  $.fn.pjax = function( container, options ) {
    if ( options )
      options.container = container;
    else
      options = $.isPlainObject(container) ? container : {
        container:container
      }

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( options.container && typeof options.container !== 'string' ) {
      throw "pjax container must be a string selector!";
      return false;
    }

    return this.live('click', function(event){
      // Middle click, cmd click, and ctrl click should open
      // links in a new tab as normal.
      if ( event.which > 1 || event.metaKey )
        return true;

      var defaults = {
        url: this.href,
        container: $(this).attr('data-pjax'),
        clickedElement: $(this),
        fragment: null,
        isform: false
      }

      $.pjax($.extend({}, defaults, options));

      event.preventDefault();
    })
  }

  // Same as pjax but for forms, also will shows query in address

  $.fn.pjaxform = function( container, options ) {
    if ( options )
      options.container = container;
    else
      options = $.isPlainObject(container) ? container : {
        container:container
      };

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( typeof options.container !== 'string' ) {
      throw "pjax container must be a string selector!";
      return false;
    }

    return this.live('submit', function(event){
      $(options.container).trigger('form-submit.pjax', $(this));
      data = $(this).serialize();
      options.type = $(this).attr('method');

      var defaults = {
        url: (options.type && options.type.toUpperCase()=='GET')?this.action+'?'+data:this.action,
        push: (options.type && options.type.toUpperCase()=='GET')?true:false,
        data: data,
        isform: true,
        container: $(this).attr('data-pjax'),
        clickedElement: $(this)
      }

      $.pjax($.extend({}, defaults, options));

      event.preventDefault();
    })
  }

  // Loads a URL with ajax, puts the response body inside a container,
  // then pushState()'s the loaded URL.
  //
  // Works just like $.ajax in that it accepts a jQuery ajax
  // settings object (with keys like url, type, data, etc).
  //
  // Accepts these extra keys:
  //
  // container - Where to stick the response body. Must be a String.
  //             $(container).html(xhr.responseBody)
  //      push - Whether to pushState the URL. Defaults to true (of course).
  //   replace - Want to use replaceState instead? That's cool.
  //
  // Use it just like $.ajax:
  //
  //   var xhr = $.pjax({ url: this.href, container: '#main' })
  //   console.log( xhr.readyState )
  //
  // Returns whatever $.ajax returns.
  var pjax = $.pjax = function( options ) {
    var $container = $(options.container),
    success = options.success || $.noop;

    // We don't want to let anyone override our success handler.
    delete options.success;

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( typeof options.container !== 'string' )
      throw "pjax container must be a string selector!";

    options = $.extend(true, {}, pjax.defaults, options);

    if ( $.isFunction(options.url) ) {
      options.url = options.url();
    }

    options.context = $container

    options.success = function(data, textStatus, jqXHR){
      if ( options.fragment ) {
        // If they specified a fragment, look for it in the response
        // and pull it out.
        var $fragment = $(data).find(options.fragment);
        if ( $fragment.length )
          data = $fragment.children();
        else
          return window.location = options.url;
      } else {
        // If we got no data or an entire web page, go directly
        // to the page and let normal error handling happen.
        if ( !$.trim(data) || /<html/i.test(data) )
          return window.location = options.url;
      }

      // Make it happen.
      this.html(data);

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var oldTitle = document.title,
      title = $.trim( this.find('title').remove().text() );
      if ( title ) document.title = title;

      var state = {
        pjax: options.container,
        fragment: options.fragment,
        timeout: options.timeout
      }

      if( $.support.pjax )
      {
        // If there are extra params, save the complete URL in the state object
        var query = $.param(options.data)
        if ( query != "_pjax=true" )
          state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query;

        if ( options.replace ) {
          window.history.replaceState(state, document.title, options.url);
        } else if ( options.push ) {
          // this extra replaceState before first push ensures good back
          // button behavior
          if ( !pjax.active ) {
            window.history.replaceState($.extend({}, state, {
              url:null
            }), oldTitle);
            pjax.active = true;
          }

          window.history.pushState(state, document.title, options.url);
        }

        // Google Analytics support
        if ( (options.replace || options.push) && window._gaq )
          _gaq.push(['_trackPageview']);

        // If the URL has a hash in it, make sure the browser
        // knows to navigate to the hash.
        var hash = window.location.hash.toString()
        if ( hash !== '' ) {
          window.location.hash = hash;
        }

      }
      else {
        // change address if it is not form or GET form
        if (!options.isform || options.type.toUpperCase()=='GET') {
          window.location.hash = "!"+options.url.replace(options.siteurl,"");
          $.hash = window.location.hash;
        }
      }

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments);
      this.trigger('success.pjax', [data, textStatus, jqXHR]);
    }

    // Cancel the current request if we're already pjaxing
    var xhr = pjax.xhr;
    if ( xhr && xhr.readyState < 4) {
      xhr.onreadystatechange = $.noop;
      xhr.abort();
    }

    pjax.xhr = $.ajax(options);
    $(document).trigger('pjax', [pjax.xhr, options]);

    return pjax.xhr;
  }

  pjax.defaults = {
    timeout: 650,
    push: true,
    replace: false,
    url: $.support.pjax ? window.location.href : window.location.hash.substr(2),
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: {
      _pjax: true
    },
    type: 'GET',
    dataType: 'html',
    siteurl : $.siteurl,
    beforeSend: function(jqXHR, settings){
      jqXHR.setRequestHeader('X-PJAX', 'true');
      jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false);
      jqXHR.setRequestHeader('X-Referer', ($.support.pjax)?window.location.href:window.location.href.replace('/#!', ''));
      this.trigger('start.pjax', [jqXHR, settings]);
    },
    error: function(jqXHR, textStatus, errorThrown){
      this.trigger('error.pjax', [jqXHR, textStatus, errorThrown]);
    },
    complete: function(jqXHR, textStatus){
      this.trigger('complete.pjax', [jqXHR, textStatus]);
    }
  }

  // Used to detect initial (useless) popstate.
  // If history.state exists, assume browser isn't going to fire initial popstate.
  var popped = ('state' in window.history), initialURL = location.href;

  // popstate handler takes care of the back and forward buttons
  //
  // You probably shouldn't use pjax on pages with other pushState
  // stuff yet.
  $(window).bind('popstate', function(event){
    // Ignore inital popstate that some browsers fire on page load
    var initialPop = !popped && location.href == initialURL;
    popped = true;
    if ( initialPop ) return;

    var state = event.state;

    if ( state && state.pjax ) {
      var container = state.pjax;
      if ( $(container+'').length )
        $.pjax({
          url: state.url || location.href,
          fragment: state.fragment,
          container: container,
          push: false,
          timeout: state.timeout
        })
      else
        window.location = location.href;
    }
  });

  // Add the state property to jQuery's event object so we can use it in
  // $(window).bind('popstate')
  if ( $.inArray('state', $.event.props) < 0 )
    $.event.props.push('state');

  // Is pjax supported by this browser?
  $.support.pjax =
  window.history && window.history.pushState && window.history.replaceState
  // pushState isn't reliable on iOS yet.
  && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/);

  // While page is loading, we should handle different URL types
  var hash = window.location.hash.toString();

  if( hash.length > 0 )
  {
    if( $.support.pjax )
      location = $.siteurl+hash.substr(2);

  }
  else if( location.pathname.length > 1 || location.search.length > 1 )
  {
    if( !$.support.pjax )
      window.location = $.siteurl+'/#!'+window.location.pathname+window.location.search;
  }

  // If there is no pjax support, we should handle hash changes
  if( !$.support.pjax )
  {
    $(window).hashchange(function(){
      hash = window.location.hash;

      if ( (hash.substr(0,2) == '#!' || hash=='') && hash != $.hash) {
        $.ajax({
          type: "GET",
          url: $.siteurl+hash.replace('#!',''),
          beforeSend : function(jqXHR, settings) {
            jqXHR.setRequestHeader('X-PJAX','true');
            jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false);
            jqXHR.setRequestHeader('X-Referer', $.hash.replace('#!',''));
            $($.container).trigger('start.pjax', [jqXHR, settings]);
          },
          success: function(data, textStatus, jqXHR){
            $($.container).html(data);
            $($.container).trigger('success.pjax', [data, textStatus, jqXHR]);
          },
          complete: function(jqXHR, textStatus){
            $($.container).trigger('complete.pjax', [jqXHR, textStatus]);
          },
          error: function(jqXHR, textStatus, errorThrown) {
            $($.container).trigger('error.pjax', [jqXHR, textStatus, errorThrown]);
          }
        });

      }
    });
    $(window).hashchange();
  }

})(jQuery);<commit_msg>define $.support.pjax at start init<commit_after>/*
 * jQuery hashchange event - v1.3 - 7/21/2010
 * http://benalman.com/projects/jquery-hashchange-plugin/
 *
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */
(function($,e,b){
  var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);
  function a(j){
    j=j||location.href;
    return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")
  }
  $.fn[c]=function(j){
    return j?this.bind(c,j):this.trigger(c)
  };

  $.fn[c].delay=50;
  g[c]=$.extend(g[c],{
    setup:function(){
      if(d){
        return false
      }
      $(f.start)
    },
    teardown:function(){
      if(d){
        return false
      }
      $(f.stop)
    }
  });
  f=(function(){
    var j={},p,m=a(),k=function(q){
      return q
    },l=k,o=k;
    j.start=function(){
      p||n()
    };

    j.stop=function(){
      p&&clearTimeout(p);
      p=b
    };

    function n(){
      var r=a(),q=o(m);
      if(r!==m){
        l(m=r,q);
        $(e).trigger(c)
      }else{
        if(q!==m){
          location.href=location.href.replace(/#.*/,"")+q
        }
      }
      p=setTimeout(n,$.fn[c].delay)
    }
    $.browser.msie&&!d&&(function(){
      var q,r;
      j.start=function(){
        if(!q){
          r=$.fn[c].src;
          r=r&&r+a();
          q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){
            r||l(a());
            n()
            }).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;
          h.onpropertychange=function(){
            try{
              if(event.propertyName==="title"){
                q.document.title=h.title
              }
            }catch(s){}
          }
        }
      };

      j.stop=k;
      o=function(){
        return a(q.location.href)
      };

      l=function(v,s){
        var u=q.document,t=$.fn[c].domain;
        if(v!==s){
          u.title=h.title;
          u.open();
          t&&u.write('<script>document.domain="'+t+'"<\/script>');
          u.close();
          q.location.hash=v
        }
      }
    })();
    return j
  })()
})(jQuery,this);

/* jquery.pjax.js with hash navigation fallback + form pjax + get forms parameters in address
 * https://github.com/imsamurai/jquery-pjax
*/

/* jquery.pjax.js with hash-navigation fallback
 * copyright andrew magalich
 * https://github.com/ckald/jquery-pjax
*/

// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/jquery-pjax

(function($){
  if (!$.hash) $.hash = '#!/';
  if (!$.siteurl) $.siteurl = document.location.protocol+'//'+document.location.host; // your site url
  if (!$.container) $.container = '#content'; // container SELECTOR to use for hash navigation

  // Is pjax supported by this browser?
  $.support.pjax =
  window.history && window.history.pushState && window.history.replaceState
  // pushState isn't reliable on iOS yet.
  && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/);

  // When called on a link, fetches the href with ajax into the
  // container specified as the first parameter or with the data-pjax
  // attribute on the link itself.
  //
  // Tries to make sure the back button and ctrl+click work the way
  // you'd expect.
  //
  // Accepts a jQuery ajax options object that may include these
  // pjax specific options:
  //
  // container - Where to stick the response body. Usually a String selector.
  //             $(container).html(xhr.responseBody)
  //      push - Whether to pushState the URL. Defaults to true (of course).
  //   replace - Want to use replaceState instead? That's cool.
  //
  // For convenience the first parameter can be either the container or
  // the options object.
  //
  // Returns the jQuery object
  $.fn.pjax = function( container, options ) {
    if ( options )
      options.container = container;
    else
      options = $.isPlainObject(container) ? container : {
        container:container
      }

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( options.container && typeof options.container !== 'string' ) {
      throw "pjax container must be a string selector!";
      return false;
    }

    return this.live('click', function(event){
      // Middle click, cmd click, and ctrl click should open
      // links in a new tab as normal.
      if ( event.which > 1 || event.metaKey )
        return true;

      var defaults = {
        url: this.href,
        container: $(this).attr('data-pjax'),
        clickedElement: $(this),
        fragment: null,
        isform: false
      }

      $.pjax($.extend({}, defaults, options));

      event.preventDefault();
    })
  }

  // Same as pjax but for forms, also will shows query in address

  $.fn.pjaxform = function( container, options ) {
    if ( options )
      options.container = container;
    else
      options = $.isPlainObject(container) ? container : {
        container:container
      };

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( typeof options.container !== 'string' ) {
      throw "pjax container must be a string selector!";
      return false;
    }

    return this.live('submit', function(event){
      $(options.container).trigger('form-submit.pjax', $(this));
      data = $(this).serialize();
      options.type = $(this).attr('method');

      var defaults = {
        url: (options.type && options.type.toUpperCase()=='GET')?this.action+'?'+data:this.action,
        push: (options.type && options.type.toUpperCase()=='GET')?true:false,
        data: data,
        isform: true,
        container: $(this).attr('data-pjax'),
        clickedElement: $(this)
      }

      $.pjax($.extend({}, defaults, options));

      event.preventDefault();
    })
  }

  // Loads a URL with ajax, puts the response body inside a container,
  // then pushState()'s the loaded URL.
  //
  // Works just like $.ajax in that it accepts a jQuery ajax
  // settings object (with keys like url, type, data, etc).
  //
  // Accepts these extra keys:
  //
  // container - Where to stick the response body. Must be a String.
  //             $(container).html(xhr.responseBody)
  //      push - Whether to pushState the URL. Defaults to true (of course).
  //   replace - Want to use replaceState instead? That's cool.
  //
  // Use it just like $.ajax:
  //
  //   var xhr = $.pjax({ url: this.href, container: '#main' })
  //   console.log( xhr.readyState )
  //
  // Returns whatever $.ajax returns.
  var pjax = $.pjax = function( options ) {
    var $container = $(options.container),
    success = options.success || $.noop;

    // We don't want to let anyone override our success handler.
    delete options.success;

    // We can't persist $objects using the history API so we must use
    // a String selector. Bail if we got anything else.
    if ( typeof options.container !== 'string' )
      throw "pjax container must be a string selector!";

    options = $.extend(true, {}, pjax.defaults, options);

    if ( $.isFunction(options.url) ) {
      options.url = options.url();
    }

    options.context = $container

    options.success = function(data, textStatus, jqXHR){
      if ( options.fragment ) {
        // If they specified a fragment, look for it in the response
        // and pull it out.
        var $fragment = $(data).find(options.fragment);
        if ( $fragment.length )
          data = $fragment.children();
        else
          return window.location = options.url;
      } else {
        // If we got no data or an entire web page, go directly
        // to the page and let normal error handling happen.
        if ( !$.trim(data) || /<html/i.test(data) )
          return window.location = options.url;
      }

      // Make it happen.
      this.html(data);

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var oldTitle = document.title,
      title = $.trim( this.find('title').remove().text() );
      if ( title ) document.title = title;

      var state = {
        pjax: options.container,
        fragment: options.fragment,
        timeout: options.timeout
      }

      if( $.support.pjax )
      {
        // If there are extra params, save the complete URL in the state object
        var query = $.param(options.data)
        if ( query != "_pjax=true" )
          state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query;

        if ( options.replace ) {
          window.history.replaceState(state, document.title, options.url);
        } else if ( options.push ) {
          // this extra replaceState before first push ensures good back
          // button behavior
          if ( !pjax.active ) {
            window.history.replaceState($.extend({}, state, {
              url:null
            }), oldTitle);
            pjax.active = true;
          }

          window.history.pushState(state, document.title, options.url);
        }

        // Google Analytics support
        if ( (options.replace || options.push) && window._gaq )
          _gaq.push(['_trackPageview']);

        // If the URL has a hash in it, make sure the browser
        // knows to navigate to the hash.
        var hash = window.location.hash.toString()
        if ( hash !== '' ) {
          window.location.hash = hash;
        }

      }
      else {
        // change address if it is not form or GET form
        if (!options.isform || options.type.toUpperCase()=='GET') {
          window.location.hash = "!"+options.url.replace(options.siteurl,"");
          $.hash = window.location.hash;
        }
      }

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments);
      this.trigger('success.pjax', [data, textStatus, jqXHR]);
    }

    // Cancel the current request if we're already pjaxing
    var xhr = pjax.xhr;
    if ( xhr && xhr.readyState < 4) {
      xhr.onreadystatechange = $.noop;
      xhr.abort();
    }

    pjax.xhr = $.ajax(options);
    $(document).trigger('pjax', [pjax.xhr, options]);

    return pjax.xhr;
  }

  pjax.defaults = {
    timeout: 650,
    push: true,
    replace: false,
    url: $.support.pjax ? window.location.href : window.location.hash.substr(2),
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: {
      _pjax: true
    },
    type: 'GET',
    dataType: 'html',
    siteurl : $.siteurl,
    beforeSend: function(jqXHR, settings){
      jqXHR.setRequestHeader('X-PJAX', 'true');
      jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false);
      jqXHR.setRequestHeader('X-Referer', ($.support.pjax)?window.location.href:window.location.href.replace('/#!', ''));
      this.trigger('start.pjax', [jqXHR, settings]);
    },
    error: function(jqXHR, textStatus, errorThrown){
      this.trigger('error.pjax', [jqXHR, textStatus, errorThrown]);
    },
    complete: function(jqXHR, textStatus){
      this.trigger('complete.pjax', [jqXHR, textStatus]);
    }
  }

  // Used to detect initial (useless) popstate.
  // If history.state exists, assume browser isn't going to fire initial popstate.
  var popped = ('state' in window.history), initialURL = location.href;

  // popstate handler takes care of the back and forward buttons
  //
  // You probably shouldn't use pjax on pages with other pushState
  // stuff yet.
  $(window).bind('popstate', function(event){
    // Ignore inital popstate that some browsers fire on page load
    var initialPop = !popped && location.href == initialURL;
    popped = true;
    if ( initialPop ) return;

    var state = event.state;

    if ( state && state.pjax ) {
      var container = state.pjax;
      if ( $(container+'').length )
        $.pjax({
          url: state.url || location.href,
          fragment: state.fragment,
          container: container,
          push: false,
          timeout: state.timeout
        })
      else
        window.location = location.href;
    }
  });

  // Add the state property to jQuery's event object so we can use it in
  // $(window).bind('popstate')
  if ( $.inArray('state', $.event.props) < 0 )
    $.event.props.push('state');

  // While page is loading, we should handle different URL types
  var hash = window.location.hash.toString();

  if( hash.length > 0 )
  {
    if( $.support.pjax )
      location = $.siteurl+hash.substr(2);

  }
  else if( location.pathname.length > 1 || location.search.length > 1 )
  {
    if( !$.support.pjax )
      window.location = $.siteurl+'/#!'+window.location.pathname+window.location.search;
  }

  // If there is no pjax support, we should handle hash changes
  if( !$.support.pjax )
  {
    $(window).hashchange(function(){
      hash = window.location.hash;

      if ( (hash.substr(0,2) == '#!' || hash=='') && hash != $.hash) {
        $.ajax({
          type: "GET",
          url: $.siteurl+hash.replace('#!',''),
          beforeSend : function(jqXHR, settings) {
            jqXHR.setRequestHeader('X-PJAX','true');
            jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false);
            jqXHR.setRequestHeader('X-Referer', $.hash.replace('#!',''));
            $($.container).trigger('start.pjax', [jqXHR, settings]);
          },
          success: function(data, textStatus, jqXHR){
            $($.container).html(data);
            $($.container).trigger('success.pjax', [data, textStatus, jqXHR]);
          },
          complete: function(jqXHR, textStatus){
            $($.container).trigger('complete.pjax', [jqXHR, textStatus]);
          },
          error: function(jqXHR, textStatus, errorThrown) {
            $($.container).trigger('error.pjax', [jqXHR, textStatus, errorThrown]);
          }
        });

      }
    });
    $(window).hashchange();
  }

})(jQuery);<|endoftext|>


<commit_before>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/pjax

(function($){

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
$.fn.pjax = function( container, options ) {
  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which > 1 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax')
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. Also ensures the back button
// works the way you'd expect.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
$.pjax = function( options ) {
  // Helper
  var $container = $(options.container)

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: { _pjax: true },
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      $container.trigger('start.pjax')
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){
      window.location = options.url
    },
    complete: function(){
      $container.trigger('end.pjax')
    },
    success: function(data){
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      if ( !$.pjax.active ) {
        $.pjax.active = true
        window.history.replaceState({ pjax: true },
                                    document.title,
                                    location.pathname)
      }

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = { pjax: options.container }

      // If there are extra params, save the complete URL in the state object
      var query = $.param(options.data)
      if ( query != "_pjax=true" )
        state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        window.history.pushState(state, document.title, options.url)
      }

      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  // We don't want to let anyone override our success handler.
  var success = options.success || $.noop
  delete options.success

  options = $.extend(true, {}, defaults, options)
  if ( $.isFunction(options.url) ) {
    options.url = options.url()
  }
  var xhr = $.ajax(options)

  $(document).trigger('pjax', xhr, options)
  return xhr
}

// Has the pjaxing begun? We must know.
$.pjax.active = false

// onpopstate fires at some point after the first page load, by design.
// pjax only cares about the back button, so we ignore the first onpopstate.
//
// Of course, older webkit doesn't fire the onopopstate event on load.
// So we have to special case. The joys.
$.pjax.firstLoad = true

if ( $.browser.webkit && parseInt($.browser.version) < 534 )
  $.pjax.firstLoad = false

// Bind our popstate handler which takes care of the back and
// forward buttons, but only once we've called pjax().
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event){
  // Do nothing if we're not pjaxing
  if ( $.pjax == $.noop )
    return

  if ( $.pjax.firstLoad )
    return $.pjax.firstLoad = false

  var state = event.state

  if ( $.pjax.active || state && state.pjax ) {
    var container = $(state.pjax+'')
    if ( container.length )
      $.pjax({
        url: state.url || location.href,
        container: container,
        push: false
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
if ( $.event.props.indexOf('state') < 0 )
  $.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  $.pjax = $.noop
  $.fn.pjax = function() { return this }
}

})(jQuery);<commit_msg>Cancel the current request if we're already pjaxing<commit_after>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/pjax

(function($){

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
$.fn.pjax = function( container, options ) {
  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which > 1 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax')
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. Also ensures the back button
// works the way you'd expect.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
$.pjax = function( options ) {
  // Helper
  var $container = $(options.container)

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: { _pjax: true },
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      $container.trigger('start.pjax')
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){
      window.location = options.url
    },
    complete: function(){
      $container.trigger('end.pjax')
    },
    success: function(data){
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      if ( !$.pjax.active ) {
        $.pjax.active = true
        window.history.replaceState({ pjax: true },
                                    document.title,
                                    location.pathname)
      }

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = { pjax: options.container }

      // If there are extra params, save the complete URL in the state object
      var query = $.param(options.data)
      if ( query != "_pjax=true" )
        state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        window.history.pushState(state, document.title, options.url)
      }

      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  // We don't want to let anyone override our success handler.
  var success = options.success || $.noop
  delete options.success

  options = $.extend(true, {}, defaults, options)

  if ( $.isFunction(options.url) ) {
    options.url = options.url()
  }

  // Cancel the current request if we're already pjaxing
  var xhr = $.pjax.xhr
  if ( xhr && xhr.readyState < 4) {
    xhr.onreadystatechange = $.noop
    xhr.abort()
  }

  $.pjax.xhr = $.ajax(options)
  $(document).trigger('pjax', $.pjax.xhr, options)

  return $.pjax.xhr
}

// Has the pjaxing begun? We must know.
$.pjax.active = false

// onpopstate fires at some point after the first page load, by design.
// pjax only cares about the back button, so we ignore the first onpopstate.
//
// Of course, older webkit doesn't fire the onopopstate event on load.
// So we have to special case. The joys.
$.pjax.firstLoad = true

if ( $.browser.webkit && parseInt($.browser.version) < 534 )
  $.pjax.firstLoad = false

// Bind our popstate handler which takes care of the back and
// forward buttons, but only once we've called pjax().
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event){
  // Do nothing if we're not pjaxing
  if ( $.pjax == $.noop )
    return

  if ( $.pjax.firstLoad )
    return $.pjax.firstLoad = false

  var state = event.state

  if ( $.pjax.active || state && state.pjax ) {
    var container = $(state.pjax+'')
    if ( container.length )
      $.pjax({
        url: state.url || location.href,
        container: container,
        push: false
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
if ( $.event.props.indexOf('state') < 0 )
  $.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  $.pjax = $.noop
  $.fn.pjax = function() { return this }
}

})(jQuery);<|endoftext|>


<commit_before>/* global describe, it */

"use strict";

var chai = require("chai"),
    expect = chai.expect;

require("chai").should();

chai.use(require("chai-as-promised"));

var isNode = (typeof global!=="undefined") && ({}.toString.call(global)==="[object global]") && (!global.document || ({}.toString.call(global.document)!=="[object HTMLDocument]")),
    IO = isNode ? require("../extra/io-node") : require("../extra/io-browser"),
    URL = "http://servercors.itsa.io/io",
    xdr, testxhr, xhr2support;

if (!isNode) {
    testxhr = new window.XMLHttpRequest(),
    xhr2support = ("withCredentials" in testxhr);
    if (!xhr2support) {
        xdr = (typeof window.XDomainRequest !== "undefined");
    }
}

describe("Response-object", function () {

    this.timeout(5000);

    it("responseText", function (done) {
        var options = {
            url: URL+"/action/responsetxt",
            method: "GET"
        };
        IO.request(options).then(
            function(response) {
                response.responseText.should.be.eql("Acknowledge responsetext ok");
                (response.readyState===4).should.be.true; // response.readyState.should.be.eql(4) --> goes wrong in IE<10 ??
                (response.status===200).should.be.true; // response.status.should.be.eql(200) --> goes wrong in IE<10 ??
                expect(response.getAllResponseHeaders()).be.a.String;
                ((response.getAllResponseHeaders().indexOf("Content-Type:")!==-1) || (response.getAllResponseHeaders().indexOf("content-type:")!==-1)).should.be.true;
                response.getResponseHeader("Content-Type").should.be.eql(xdr ? "text/plain" : "text/plain; charset=utf-8");
                done();
            }
        ).then(
            undefined,
            done
        );
    });

});

describe("Analysing params", function () {

    this.timeout(5000);

    describe("GET-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "GET"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "GET",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "GET",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "GET",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("DELETE-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy=5",
                method: "DELETE"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "DELETE",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy=5",
                method: "DELETE",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "DELETE",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("PUT-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "PUT"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: undefined");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "PUT",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "PUT",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: undefined");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "PUT",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("POST-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "POST"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: undefined");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "POST",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "POST",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: undefined");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "POST",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("Aborting requests", function () {

        it("aborting", function (done) {
            var options = {
                url: URL+"/action/responsedelayed",
                method: "GET"
            };
            var io = IO.request(options);
            io.then(
                function() {
                    done(new Error("io should not be fulfilled"));
                },
                function() {
                    done();
                }
            );
            setTimeout(function() {
                io.abort();
            }, 50);
        });

        it("aborting by timeout", function (done) {
            var options = {
                url: URL+"/action/responsedelayed",
                method: "GET",
                timeout: 100
            };
            var io = IO.request(options);
            io.then(
                function() {
                    done(new Error("io should not be fulfilled"));
                },
                function() {
                    done();
                }
            );
        });

    });

});
<commit_msg>updated tests<commit_after>/* global describe, it */

"use strict";

var chai = require("chai"),
    expect = chai.expect;

require("chai").should();

chai.use(require("chai-as-promised"));

var isNode = (typeof global!=="undefined") && ({}.toString.call(global)==="[object global]") && (!global.document || ({}.toString.call(global.document)!=="[object HTMLDocument]")),
    IO = isNode ? require("../extra/io-node") : require("../extra/io-browser"),
    URL = "http://servercors.itsa.io/io",
    xdr, testxhr, xhr2support;

if (!isNode) {
    testxhr = new window.XMLHttpRequest(),
    xhr2support = ("withCredentials" in testxhr);
    if (!xhr2support) {
        xdr = (typeof window.XDomainRequest !== "undefined");
    }
}

describe("Response-object", function () {

    this.timeout(5000);

    it("responseText", function (done) {
        var options = {
            url: URL+"/action/responsetxt",
            method: "GET"
        };
        IO.request(options).then(
            function(response) {
                response.responseText.should.be.eql("Acknowledge responsetext ok");
                (response.readyState===4).should.be.true; // response.readyState.should.be.eql(4) --> goes wrong in IE<10 ??
                (response.status===200).should.be.true; // response.status.should.be.eql(200) --> goes wrong in IE<10 ??
                expect(response.getAllResponseHeaders()).be.a.String;
                ((response.getAllResponseHeaders().indexOf("Content-Type:")!==-1) || (response.getAllResponseHeaders().indexOf("content-type:")!==-1)).should.be.true;
                response.getResponseHeader("Content-Type").should.be.eql(xdr ? "text/plain" : "text/plain; charset=utf-8");
                done();
            }
        ).then(
            undefined,
            done
        );
    });

});

describe("Analysing params", function () {

    this.timeout(5000);

    describe("GET-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "GET"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "GET",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "GET",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "GET",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge get-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("DELETE-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy=5",
                method: "DELETE"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "DELETE",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy=5",
                method: "DELETE",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "DELETE",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "get" : "delete")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("PUT-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "PUT"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "PUT",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "PUT",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "PUT",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge "+(xdr ? "post" : "put")+"-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("POST-params", function () {

        it("through querystring", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "POST"
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("through dataobject", function (done) {
            var options = {
                url: URL,
                method: "POST",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 1", function (done) {
            var options = {
                url: URL+"?data=25&dummy1=5",
                method: "POST",
                data: {dummy2: 10}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

        it("combined 2", function (done) {
            var options = {
                url: URL+"?dummy1=5&dummy2=10",
                method: "POST",
                data: {data: 25}
            };
            IO.request(options).then(
                function(response) {
                    response.responseText.should.be.eql("Acknowledge post-request with data: 25");
                    done();
                }
            ).then(
                undefined,
                done
            );
        });

    });

    describe("Aborting requests", function () {

        it("aborting", function (done) {
            var options = {
                url: URL+"/action/responsedelayed",
                method: "GET"
            };
            var io = IO.request(options);
            io.then(
                function() {
                    done(new Error("io should not be fulfilled"));
                },
                function() {
                    done();
                }
            );
            setTimeout(function() {
                io.abort();
            }, 50);
        });

        it("aborting by timeout", function (done) {
            var options = {
                url: URL+"/action/responsedelayed",
                method: "GET",
                timeout: 100
            };
            var io = IO.request(options);
            io.then(
                function() {
                    done(new Error("io should not be fulfilled"));
                },
                function() {
                    done();
                }
            );
        });

    });

});
<|endoftext|>


<commit_before>// Copyright 2014-2019, University of Colorado Boulder

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffProperty, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const ButtonListener = require( 'SCENERY/input/ButtonListener' );
  const Dimension2 = require( 'DOT/Dimension2' );
  const inherit = require( 'PHET_CORE/inherit' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const Line = require( 'SCENERY/nodes/Line' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const OnOffSwitch = require( 'SUN/OnOffSwitch' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<*>} property stores the value of the current choice
   * @param {*} valueA value for choice 'A'
   * @param {Node} labelA label for choice 'A'
   * @param {*} valueB value for choice 'B'
   * @param {Node} labelB label for choice 'B'
   * @param {Object} [options]
   * @constructor
   */
  function ABSwitch( property, valueA, labelA, valueB, labelB, options ) {

    // default option values
    options = merge( {
      switchSize: new Dimension2( 60, 30 ),
      xSpacing: 8,
      cursor: 'pointer',
      centerOnButton: false,

      // uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled
      setEnabled: function( label, enabled ) { label.opacity = enabled ? 1.0 : 0.5; },

      // pointer areas for thumb
      thumbTouchAreaXDilation: 8,
      thumbTouchAreaYDilation: 8,
      thumbMouseAreaXDilation: 0,
      thumbMouseAreaYDilation: 0,
      tandem: Tandem.REQUIRED
    }, options );

    const defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'rgb(40,40,40)' ).addColorStop( 1, 'rgb(200,200,200)' );
    options.trackFillA = options.trackFillA || defaultTrackFill;
    options.trackFillB = options.trackFillB || defaultTrackFill;
    options.thumbFill = options.thumbFill ||
                        new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(200,200,200)' );

    Node.call( this );

    // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
    const onProperty = new Property( valueB === property.get() );

    const onOffSwitch = new OnOffSwitch( onProperty, {
      size: options.switchSize,
      cursor: options.cursor,
      thumbFill: options.thumbFill,
      trackOnFill: options.trackFillB,
      trackOffFill: options.trackFillA,
      thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
      thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
      thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
      thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
      tandem: options.tandem.createTandem( 'onOffSwitch' )
    } );

    // rendering order
    this.addChild( onOffSwitch );
    this.addChild( labelA );
    this.addChild( labelB );

    // layout: 'A' on the left, 'B' on the right
    labelA.right = onOffSwitch.left - options.xSpacing;
    labelA.centerY = onOffSwitch.centerY;
    labelB.left = onOffSwitch.right + options.xSpacing;
    labelB.centerY = onOffSwitch.centerY;

    // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
    if ( options.centerOnButton ) {
      const additionalWidth = Math.abs( labelA.width - labelB.width );
      const strut = new Line( 0, 0, this.width + additionalWidth, 0 );
      this.addChild( strut );
      strut.moveToBack();
      if ( labelA.width < labelB.width ) {
        strut.left = labelA.left - ( additionalWidth / 2 );
      }
      else {
        strut.left = labelA.left;
      }
    }

    // sync properties, listeners must be disposed
    const propertyListener = function( object ) {
      onProperty.set( valueB === object );
    };
    property.link( propertyListener );

    const onPropertyListener = function( on ) {
      property.set( on ? valueB : valueA );
      if ( options.setEnabled ) {
        options.setEnabled( labelA, !on );
        options.setEnabled( labelB, on );
      }
    };
    onProperty.link( onPropertyListener );

    // click on labels to select, must be disposed
    const aInputListener = new ButtonListener( {
      fire: function() { onProperty.set( false ); }
    } );
    const bInputListener = new ButtonListener( {
      fire: function() { onProperty.set( true ); }
    } );
    labelA.addInputListener( aInputListener );
    labelB.addInputListener( bInputListener );

    // @private - for dispose
    this.disposeABSwitch = function() {
      property.unlink( propertyListener );
      onProperty.unlink( onPropertyListener );
      labelA.removeInputListener( aInputListener );
      labelB.removeInputListener( bInputListener );
    };

    this.mutate( options );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ABSwitch', this );
  }

  sun.register( 'ABSwitch', ABSwitch );

  return inherit( Node, ABSwitch, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.disposeABSwitch();
      Node.prototype.dispose.call( this );
    }
  } );
} );<commit_msg>Instrumentation for user interface components, see https://github.com/phetsims/balancing-act/issues/96<commit_after>// Copyright 2014-2019, University of Colorado Boulder

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffProperty, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const ButtonListener = require( 'SCENERY/input/ButtonListener' );
  const Dimension2 = require( 'DOT/Dimension2' );
  const inherit = require( 'PHET_CORE/inherit' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const Line = require( 'SCENERY/nodes/Line' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const OnOffSwitch = require( 'SUN/OnOffSwitch' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<*>} property stores the value of the current choice
   * @param {*} valueA value for choice 'A'
   * @param {Node} labelA label for choice 'A'
   * @param {*} valueB value for choice 'B'
   * @param {Node} labelB label for choice 'B'
   * @param {Object} [options]
   * @constructor
   */
  function ABSwitch( property, valueA, labelA, valueB, labelB, options ) {

    // default option values
    options = merge( {
      switchSize: new Dimension2( 60, 30 ),
      xSpacing: 8,
      cursor: 'pointer',
      centerOnButton: false,

      // uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled
      setEnabled: function( label, enabled ) { label.opacity = enabled ? 1.0 : 0.5; },

      // pointer areas for thumb
      thumbTouchAreaXDilation: 8,
      thumbTouchAreaYDilation: 8,
      thumbMouseAreaXDilation: 0,
      thumbMouseAreaYDilation: 0,
      tandem: Tandem.REQUIRED
    }, options );

    const defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'rgb(40,40,40)' ).addColorStop( 1, 'rgb(200,200,200)' );
    options.trackFillA = options.trackFillA || defaultTrackFill;
    options.trackFillB = options.trackFillB || defaultTrackFill;
    options.thumbFill = options.thumbFill ||
                        new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(200,200,200)' );

    Node.call( this );

    // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
    const onProperty = new Property( valueB === property.get() );

    const onOffSwitch = new OnOffSwitch( onProperty, {
      size: options.switchSize,
      cursor: options.cursor,
      thumbFill: options.thumbFill,
      trackOnFill: options.trackFillB,
      trackOffFill: options.trackFillA,
      thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
      thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
      thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
      thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
      tandem: options.tandem.createTandem( 'onOffSwitch' )
    } );

    // rendering order
    this.addChild( onOffSwitch );
    this.addChild( labelA );
    this.addChild( labelB );

    // layout: 'A' on the left, 'B' on the right
    labelA.right = onOffSwitch.left - options.xSpacing;
    labelA.centerY = onOffSwitch.centerY;
    labelB.left = onOffSwitch.right + options.xSpacing;
    labelB.centerY = onOffSwitch.centerY;

    // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
    if ( options.centerOnButton ) {
      const additionalWidth = Math.abs( labelA.width - labelB.width );
      const strut = new Line( 0, 0, this.width + additionalWidth, 0 );
      this.addChild( strut );
      strut.moveToBack();
      if ( labelA.width < labelB.width ) {
        strut.left = labelA.left - ( additionalWidth / 2 );
      }
      else {
        strut.left = labelA.left;
      }
    }

    // sync properties, listeners must be disposed
    const propertyListener = function( object ) {
      onProperty.set( valueB === object );
    };
    property.link( propertyListener );

    const onPropertyListener = function( on ) {
      property.set( on ? valueB : valueA );
      if ( options.setEnabled ) {
        options.setEnabled( labelA, !on );
        options.setEnabled( labelB, on );
      }
    };
    onProperty.link( onPropertyListener );

    // click on labels to select, must be disposed
    const aInputListener = new ButtonListener( {
      fire: function() { onProperty.set( false ); },
      tandem: options.tandem.createTandem( 'aInputListener' )
    } );
    const bInputListener = new ButtonListener( {
      fire: function() { onProperty.set( true ); },
      tandem: options.tandem.createTandem( 'bInputListener' )
    } );
    labelA.addInputListener( aInputListener );
    labelB.addInputListener( bInputListener );

    // @private - for dispose
    this.disposeABSwitch = function() {
      property.unlink( propertyListener );
      onProperty.unlink( onPropertyListener );
      labelA.removeInputListener( aInputListener );
      labelB.removeInputListener( bInputListener );
    };

    this.mutate( options );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ABSwitch', this );
  }

  sun.register( 'ABSwitch', ABSwitch );

  return inherit( Node, ABSwitch, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.disposeABSwitch();
      Node.prototype.dispose.call( this );
    }
  } );
} );<|endoftext|>