<commit_before>define(['logManager'], function (logManager) {

  "use strict";

  var utils = function (core, META) {
    this.core = core;
    this.META = META;
    this.logger = logManager.create('utils');
  };

  utils.prototype.exists = function (initial_node, component_path, next) {
    var self = this;
    self.logger.info('exists()');

    var dirs = self.get_dirs(component_path);
    var curr_node = initial_node;
    check_component(0);

    function check_component (index) {
      if (index >= dirs.length) {
        next(true);
      } else {
        self.core.loadChildren(curr_node, function (err, children) {
          if (err) {
            self.logger.error(err);
            next(false);
          } else {
            var dir_node = null;
            for (var j = children.length - 1; j >= 0; j--) {
              var name = self.core.getAttribute(children[j], 'name');
              if (name === dirs[index]) {
                dir_node = children[j];
              }
            }
            if (dir_node == null) {
              return next(false);
            }
            curr_node = dir_node;
            check_component(index+1);
          }
        });
      }
    }

  };

  utils.prototype.md = function (initial_node, component_path, next) {
    var self = this;
    self.logger.info('md()');

    var dirs = self.get_dirs(component_path);
    var curr_node = initial_node;
    create_directory(0);

    function create_directory (index) {
      if (index >= dirs.length) {
        next(curr_node);
      } else {
        self.core.loadChildren(curr_node, function (err, children) {
          var dir_node = null;
          for (var j = children.length - 1; j >= 0; j--) {
            var name = self.core.getAttribute(children[j], 'name');
            if (name === dirs[index]) {
              dir_node = children[j];
            }
          }
          // TODO: Once we found a non-existent directory, we can create the rest
          if (dir_node == null) {
            dir_node = self.core.createNode({
              base: self.META.Folder,
              parent: curr_node
            });
            self.core.setAttribute(dir_node, 'name', dirs[index]);
          }
          curr_node = dir_node;
          create_directory(index+1);
        });
      }
    }

  };

  utils.prototype.get_dirs = function (component_path) {
    var path = require('path');
    var ext = path.extname(component_path);
    var dirs = '';
    if (ext === '') dirs = component_path;
    if (ext === '.nc') dirs = path.dirname(component_path);
    return dirs.split('/');
  };

  return utils;
});
<commit_msg>Add for_each_then_call_next<commit_after>define(['logManager'], function (logManager) {

  "use strict";

  var utils = function (core, META) {
    this.core = core;
    this.META = META;
    this.logger = logManager.create('utils');
  };

  utils.prototype.for_each_then_call_next = function (object, apply, next) {
    var self = this;
    self.logger.info('for_each_then_call_next()');
    var counter = 0, objects_length = Object.keys(object).length;
    for (var key in object) {
      apply(object[key], function () {
        counter++;
        if (counter >= objects_length) {
          next();
        }
      });
    }
  };

  utils.prototype.exists = function (initial_node, component_path, next) {
    var self = this;
    self.logger.info('exists()');

    var dirs = self.get_dirs(component_path);
    var curr_node = initial_node;
    check_component(0);

    function check_component (index) {
      if (index >= dirs.length) {
        next(true);
      } else {
        self.core.loadChildren(curr_node, function (err, children) {
          if (err) {
            self.logger.error(err);
            next(false);
          } else {
            var dir_node = null;
            for (var j = children.length - 1; j >= 0; j--) {
              var name = self.core.getAttribute(children[j], 'name');
              if (name === dirs[index]) {
                dir_node = children[j];
              }
            }
            if (dir_node == null) {
              return next(false);
            }
            curr_node = dir_node;
            check_component(index+1);
          }
        });
      }
    }

  };

  utils.prototype.md = function (initial_node, component_path, next) {
    var self = this;
    self.logger.info('md()');

    var dirs = self.get_dirs(component_path);
    var curr_node = initial_node;
    create_directory(0);

    function create_directory (index) {
      if (index >= dirs.length) {
        next(curr_node);
      } else {
        self.core.loadChildren(curr_node, function (err, children) {
          var dir_node = null;
          for (var j = children.length - 1; j >= 0; j--) {
            var name = self.core.getAttribute(children[j], 'name');
            if (name === dirs[index]) {
              dir_node = children[j];
            }
          }
          // TODO: Once we found a non-existent directory, we can create the rest
          if (dir_node == null) {
            dir_node = self.core.createNode({
              base: self.META.Folder,
              parent: curr_node
            });
            self.core.setAttribute(dir_node, 'name', dirs[index]);
          }
          curr_node = dir_node;
          create_directory(index+1);
        });
      }
    }

  };

  utils.prototype.get_dirs = function (component_path) {
    var path = require('path');
    var ext = path.extname(component_path);
    var dirs = '';
    if (ext === '') dirs = component_path;
    if (ext === '.nc') dirs = path.dirname(component_path);
    return dirs.split('/');
  };

  return utils;
});
<|endoftext|>


<commit_before>(function (Q, $) {

/**
 * Streams Tools
 * @module Streams-tools
 * @main
 */

/**
 * Interface for selecting an app user
 * @class Streams userChooser
 * @constructor
 * @param {Object} [options] this object contains function parameters
 *   @param {Q.Event} [options.onChoose] is triggered with (userId, avatar)
 *       parameters when a user is chosen
 *   @param {Number} [options.delay=500] how long to delay before sending a request
 *    to allow more characters to be entered
 *   @param {bool} [options.isCommunity=false] If true, search communities instead regular users
 *   @param {Object} [options.exclude] hash of {userId: true},
 *    where userId are the ids of the users to exclude from the results.
 *    Defaults to id of logged-in user, if logged in.
 */
Q.Tool.define("Streams/userChooser", function(o) {
	Q.plugins.Streams.cache = Q.plugins.Streams.cache || {};
    Q.plugins.Streams.cache.userChooser = Q.plugins.Streams.cache.userChooser || {};

	var tool = this;

	tool.onChoose = o.onChoose;
	tool.delay = o.delay;
	tool.exclude = o.exclude;

	var element = $(this.element);
	this.$input = $('input', element);
	var cached = {};
	var focusedResults = false;
	tool.$results = $('
')
		.css({
			display: 'none',
			position: 'absolute',
			left: tool.$input.offset().left + 'px',
			top: tool.$input.offset().top + tool.$input.outerHeight() + 'px',
			width: tool.$input.outerWidth(),
			'z-index': 80000,
			background: 'white',
			border: 'solid 1px #99a',
			'tab-index': 9000
		}).on(Q.Pointer.start.eventName + ' focusin', function () {
			focusedResults = true;
		}).appendTo('body');

	var t = null;
	tool.$input.on('blur', function (event) {
		setTimeout(function () {
			if (!focusedResults) {
				tool.$results.remove();
			} else {
				function _handlePointerEnd() {
					tool.$results.remove();
					$(document).off(Q.Pointer.end, _handlePointerEnd);
				}
				$(document).on(Q.Pointer.end, tool, function () {
					setTimeout(_handlePointerEnd, 0);
				});
			}
			focusedResults = false;
		}, 10);
	}).on('focus change', doQuery);
	element.on('keyup keydown', doQuery);

	var lastQuery = null;

	function doQuery(event) {

		var cur = $('.Q_selected', tool.$results);
		var query = tool.$input.val();

		switch (event.keyCode) {
			case 38: // up arrow
				if (event.type === 'keyup') {
					return;
				}
				var prev = cur.prev();
				if (!prev.length) {
					prev = tool.$results.children().last();
				}
				tool.$results.children().removeClass('Q_selected');
				prev.addClass('Q_selected');
				return false;
			case 40: // down arrow
				if (event.type === 'keyup') {
					return;
				}
				var next = cur.next();
				if (!next.length) {
					next = tool.$results.children().first();
				}
				tool.$results.children().removeClass('Q_selected');
				next.addClass('Q_selected');
				return false;
			case 13: // enter
				if (event.type === 'keyup') {
					return;
				}
				if (cur) {
					onChoose(cur);
				}
				return false;
			default:
				if (query === lastQuery) {
					return;
				}
				lastQuery = query;
				if (event.type === 'keydown') {
					return;
				}
				if (!query) {
					tool.$results.remove();
					return;
				}
				tool.$input.css({
					'background-image': 'url(' +Q.url('/{{Q}}/img/throbbers/loading.gif') + ')',
					'background-repeat': 'no-repeat'
				});
				Q.Streams.Avatar.byPrefix(tool.$input.val().toLowerCase(), onResponse, {'public': true});
		}

		function onChoose (cur) {
			var userId = cur.data('userId');
			var avatar = cur.data('avatar');
			tool.$input.blur().val('');
			Q.handle(tool.onChoose, this, [userId, avatar]);
			tool.end();
		}

		function onResponse (err, avatars) {
			tool.$input.css('background-image', 'none');
			if (err) {
				return; // silently return
			}
			if (Q.isEmpty(avatars)) {
				return tool.$results.remove();
			}
			tool.$results.empty();
			var show = 0;
			for (var k in avatars) {
				if (k in tool.exclude && tool.exclude[k]) {
					continue;
				}

				if ((tool.state.isCommunity && !Q.Users.isCommunityId(k)) || (!tool.state.isCommunity && Q.Users.isCommunityId(k))) {
					continue;
				}

				var result = $('').append(
					$('')
					.attr('src', Q.plugins.Users.iconUrl(avatars[k].icon, 40))
				).append(
					$('').html(avatars[k].displayName())
				).on(Q.Pointer.enter, function () {
					$('*', tool.$results).removeClass('Q_selected');
					$(this).addClass('Q_selected');
				}).on(Q.Pointer.leave, function () {
					$('*', tool.$results).removeClass('Q_selected');
					$(this).addClass('Q_selected');
				}).on(Q.Pointer.fastclick, function () {
					onChoose($(this));
				}).data('userId', k)
				.data('avatar', avatars[k])
				.on(Q.Pointer.start.eventName + ' focusin', function () {
					focusedResults = true;
				}).appendTo(tool.$results);
				if (!show) {
					result.addClass('Q_selected');
				}
				++show;
			}
			if (show) {
				tool.$results.css({
					left: tool.$input.offset().left + 'px',
					top: tool.$input.offset().top + tool.$input.outerHeight() + 'px',
					width: tool.$input.outerWidth()
				}).appendTo('body').show();
			} else {
				tool.$results.remove();
			}
		}
	}

},

{
	onChoose: new Q.Event(),
	delay: 500,
	isCommunity: false,
	exclude: {}
},

{
	end: function () {
		this.$input.blur().trigger('Q_refresh');
		this.$results.remove();
	}
}

);

})(Q, jQuery);<commit_msg>renamed from isCommunity to communitiesOnly<commit_after>(function (Q, $) {

/**
 * Streams Tools
 * @module Streams-tools
 * @main
 */

/**
 * Interface for selecting an app user
 * @class Streams userChooser
 * @constructor
 * @param {Object} [options] this object contains function parameters
 *   @param {Q.Event} [options.onChoose] is triggered with (userId, avatar)
 *       parameters when a user is chosen
 *   @param {Number} [options.delay=500] how long to delay before sending a request
 *    to allow more characters to be entered
 *   @param {bool} [options.communitiesOnly=false] If true, search communities instead regular users
 *   @param {Object} [options.exclude] hash of {userId: true},
 *    where userId are the ids of the users to exclude from the results.
 *    Defaults to id of logged-in user, if logged in.
 */
Q.Tool.define("Streams/userChooser", function(o) {
	Q.plugins.Streams.cache = Q.plugins.Streams.cache || {};
    Q.plugins.Streams.cache.userChooser = Q.plugins.Streams.cache.userChooser || {};

	var tool = this;

	tool.onChoose = o.onChoose;
	tool.delay = o.delay;
	tool.exclude = o.exclude;

	var element = $(this.element);
	this.$input = $('input', element);
	var cached = {};
	var focusedResults = false;
	tool.$results = $('
')
		.css({
			display: 'none',
			position: 'absolute',
			left: tool.$input.offset().left + 'px',
			top: tool.$input.offset().top + tool.$input.outerHeight() + 'px',
			width: tool.$input.outerWidth(),
			'z-index': 80000,
			background: 'white',
			border: 'solid 1px #99a',
			'tab-index': 9000
		}).on(Q.Pointer.start.eventName + ' focusin', function () {
			focusedResults = true;
		}).appendTo('body');

	var t = null;
	tool.$input.on('blur', function (event) {
		setTimeout(function () {
			if (!focusedResults) {
				tool.$results.remove();
			} else {
				function _handlePointerEnd() {
					tool.$results.remove();
					$(document).off(Q.Pointer.end, _handlePointerEnd);
				}
				$(document).on(Q.Pointer.end, tool, function () {
					setTimeout(_handlePointerEnd, 0);
				});
			}
			focusedResults = false;
		}, 10);
	}).on('focus change', doQuery);
	element.on('keyup keydown', doQuery);

	var lastQuery = null;

	function doQuery(event) {

		var cur = $('.Q_selected', tool.$results);
		var query = tool.$input.val();

		switch (event.keyCode) {
			case 38: // up arrow
				if (event.type === 'keyup') {
					return;
				}
				var prev = cur.prev();
				if (!prev.length) {
					prev = tool.$results.children().last();
				}
				tool.$results.children().removeClass('Q_selected');
				prev.addClass('Q_selected');
				return false;
			case 40: // down arrow
				if (event.type === 'keyup') {
					return;
				}
				var next = cur.next();
				if (!next.length) {
					next = tool.$results.children().first();
				}
				tool.$results.children().removeClass('Q_selected');
				next.addClass('Q_selected');
				return false;
			case 13: // enter
				if (event.type === 'keyup') {
					return;
				}
				if (cur) {
					onChoose(cur);
				}
				return false;
			default:
				if (query === lastQuery) {
					return;
				}
				lastQuery = query;
				if (event.type === 'keydown') {
					return;
				}
				if (!query) {
					tool.$results.remove();
					return;
				}
				tool.$input.css({
					'background-image': 'url(' +Q.url('/{{Q}}/img/throbbers/loading.gif') + ')',
					'background-repeat': 'no-repeat'
				});
				Q.Streams.Avatar.byPrefix(tool.$input.val().toLowerCase(), onResponse, {'public': true});
		}

		function onChoose (cur) {
			var userId = cur.data('userId');
			var avatar = cur.data('avatar');
			tool.$input.blur().val('');
			Q.handle(tool.onChoose, this, [userId, avatar]);
			tool.end();
		}

		function onResponse (err, avatars) {
			tool.$input.css('background-image', 'none');
			if (err) {
				return; // silently return
			}
			if (Q.isEmpty(avatars)) {
				return tool.$results.remove();
			}
			tool.$results.empty();
			var show = 0;
			for (var k in avatars) {
				if (k in tool.exclude && tool.exclude[k]) {
					continue;
				}

				if ((tool.state.communitiesOnly && !Q.Users.isCommunityId(k)) || (!tool.state.communitiesOnly && Q.Users.isCommunityId(k))) {
					continue;
				}

				var result = $('').append(
					$('')
					.attr('src', Q.plugins.Users.iconUrl(avatars[k].icon, 40))
				).append(
					$('').html(avatars[k].displayName())
				).on(Q.Pointer.enter, function () {
					$('*', tool.$results).removeClass('Q_selected');
					$(this).addClass('Q_selected');
				}).on(Q.Pointer.leave, function () {
					$('*', tool.$results).removeClass('Q_selected');
					$(this).addClass('Q_selected');
				}).on(Q.Pointer.fastclick, function () {
					onChoose($(this));
				}).data('userId', k)
				.data('avatar', avatars[k])
				.on(Q.Pointer.start.eventName + ' focusin', function () {
					focusedResults = true;
				}).appendTo(tool.$results);
				if (!show) {
					result.addClass('Q_selected');
				}
				++show;
			}
			if (show) {
				tool.$results.css({
					left: tool.$input.offset().left + 'px',
					top: tool.$input.offset().top + tool.$input.outerHeight() + 'px',
					width: tool.$input.outerWidth()
				}).appendTo('body').show();
			} else {
				tool.$results.remove();
			}
		}
	}

},

{
	onChoose: new Q.Event(),
	delay: 500,
	communitiesOnly: false,
	exclude: {}
},

{
	end: function () {
		this.$input.blur().trigger('Q_refresh');
		this.$results.remove();
	}
}

);

})(Q, jQuery);<|endoftext|>


<commit_before>var files = require('../files.js');
var selftest = require('../selftest.js');
var _ = require('underscore');
var Sandbox = selftest.Sandbox;

selftest.define("windows prints correct message when it can't do mobile things", ["windows"], function () {
  var s = new Sandbox();

  var run;

  s.createApp("myapp", "standard-app");
  s.cd("myapp", function () {
    _.each(['add-platform'], function (command) {
      _.each(['ios', 'android'], function (platform) {
        run = s.run(command, platform);
        // We print some warning that involves saying you're on Windows
        run.matchErr("Windows");
      });
    });
  });

  s.run("create", "--example", "todos");
  s.cd("todos", function () {
    run = s.run("build");
    run.matchErr("Windows");
    _.each(['ios', 'android', 'ios-device', 'android-device'], function (platform) {
      run = s.run('run', platform);
      // We print some warning that involves saying you're on Windows
      run.matchErr("Windows");
    });
  });
});
<commit_msg>Refine the windows cordova script once again<commit_after>var files = require('../files.js');
var selftest = require('../selftest.js');
var _ = require('underscore');
var Sandbox = selftest.Sandbox;

selftest.define("windows prints correct message when it can't do mobile things", ["windows"], function () {
  var s = new Sandbox();

  var run;

  s.createApp("myapp", "standard-app");
  s.cd("myapp", function () {
    _.each(['add-platform'], function (command) {
      _.each(['ios', 'android'], function (platform) {
        run = s.run(command, platform);
        // We print some warning that involves saying you're on Windows
        run.matchErr("Windows");
      });
    });
  });

  s.run("create", "--example", "todos");
  s.cd("todos", function () {
    run = s.run("build", ".build", "--server", "https://foo.com");
    run.matchErr("Windows");
    _.each(['ios', 'android', 'ios-device', 'android-device'], function (platform) {
      run = s.run('run', platform);
      // We print some warning that involves saying you're on Windows
      run.matchErr("Windows");
    });
    _.each(['ios', 'android'], function (platform) {
      run = s.run('remove-platform', platform);
      run.match('removed platform');
    });
  });
});
<|endoftext|>


<commit_before>function makeAPIRequest(textToTranslate) {
    var params = {
        "lang": "en-ru",
        "text": textToTranslate,
        "flags": "5"

    }
    return $.get("http://translate.yandex.net/dicservice.json/lookup", params);
}

function getSelectionAttributes(selectionObj) {
    var range = selectionObj.getRangeAt(0);
    range.collapse(false);

    var dummy = document.createElement("span");
    range.insertNode(dummy);

    var dummyHeight = $(dummy).outerHeight();
    var coordinates = $(dummy).offset();
    dummy.parentNode.removeChild(dummy);

    return {
        left: coordinates.left,
        top: coordinates.top,
        height: dummyHeight
    };
}

$(document).keypress(function (event) {
    if (event.which == 13) {
        var selectionObj = window.getSelection();
        var req = makeAPIRequest(selectionObj.toString());
        req.done(function (data) {
            var selectionAttributes = getSelectionAttributes(selectionObj);

            output = Templates.Popover.render({
                title: selectionObj.toString(),
                translation: data.def[0].tr[0].text,
                offset_x: selectionAttributes.left,
                offset_y: selectionAttributes.top
            });

            $("#kolyanlab_slovari").remove();
            $("body").append(output);

            var popover = $("#kolyanlab_slovari");
            width = popover.outerWidth(true);
            height = popover.outerHeight(true);

            popover.css('left', selectionAttributes.left - width/2.)
            popover.css('top', selectionAttributes.top - height - selectionAttributes.height)
         })
    }
});
<commit_msg>1. Small refactoring. Extract showPopover function. 2. Very simple message if translation of the word was not found. 3. Close popover on click.<commit_after>function makeAPIRequest(textToTranslate) {
    var params = {
        "lang": "en-ru",
        "text": textToTranslate,
        "flags": "5"

    }
    return $.get("http://translate.yandex.net/dicservice.json/lookup", params);
}

function getSelectionAttributes(selectionObj) {
    var range = selectionObj.getRangeAt(0);
    range.collapse(false);

    var dummy = document.createElement("span");
    range.insertNode(dummy);

    var dummyHeight = $(dummy).outerHeight();
    var coordinates = $(dummy).offset();
    dummy.parentNode.removeChild(dummy);

    return {
        left: coordinates.left,
        top: coordinates.top,
        height: dummyHeight
    };
}

function showPopover(selectionAttributes, outputHTML) {
    $("#kolyanlab_slovari").remove();
    $("body").append(outputHTML);

    var popover = $("#kolyanlab_slovari");
    popover.click(function () {
        $(this).hide();
    })

    width = popover.outerWidth(true);
    height = popover.outerHeight(true);

    popover.css('left', selectionAttributes.left - width/2.)
    popover.css('top', selectionAttributes.top - height - selectionAttributes.height)
}

$(document).keypress(function (event) {
    if (event.which == 13) {
        var selectionObj = window.getSelection();
        var req = makeAPIRequest(selectionObj.toString());
        req.done(function (data) {
            var selectionAttributes = getSelectionAttributes(selectionObj);

            var translation;
            if (data.def.length > 0) {
                translation = data.def[0].tr[0].text;
            }
            else {
                translation = "<В словаре не найдено>";
            }

            outputHTML = Templates.Popover.render({
                title: selectionObj.toString(),
                translation: translation,
                offset_x: 0,
                offset_y: 0
            });

            showPopover(selectionAttributes, outputHTML);
         });
    }
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import InboundActionsMixin from 'ember-component-inbound-actions/inbound-actions';
import DomMixin from 'ember-lifeline/mixins/dom';
import layout from '../templates/components/ember-scrollable';
import {Horizontal, Vertical} from '../classes/scrollable';

const {
  computed,
  deprecate,
  isPresent,
  run: {
    scheduleOnce,
    debounce,
    throttle
  },
  $
} = Ember;

const hideDelay = Ember.testing ? 16 : 1000;
const PAGE_JUMP_MULTIPLE = 7 / 8;

const THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS = 1; // 60 fps -> 1 sec / 60 = 16ms

const scrollbarSelector = '.tse-scrollbar';
const scrollContentSelector = '.tse-scroll-content';
const contentSelector = '.tse-content';

export default Ember.Component.extend(InboundActionsMixin, DomMixin, {
  layout,
  classNameBindings: [':ember-scrollable', ':tse-scrollable', 'horizontal', 'vertical'],

  /**
   * If true, a scrollbar will be shown horizontally
   *
   * @property horizontal
   * @public
   * @type Boolean
   * @default false
   */
  horizontal: null,

  /**
   * If true, a scrollbar will be shown vertically
   *
   * @property vertical
   * @public
   * @type Boolean
   */
  vertical: null,
  /**
   * Indicates whether the scrollbar should auto hide after a given period of time (see hideDelay),
   * or remain persitent alongside the content to be scrolled.
   *
   * @property autoHide
   * @public
   * @type Boolean
   * @default true
   */
  autoHide: true,
  scrollBuffer: 50,
  /**
   * Number indicating offset from anchor point (top for vertical, left for horizontal) where the scroll handle
   * should be rendered.
   *
   * @property scrollTo
   * @public
   * @type Number
   */
  scrollTo: computed('vertical', {
    get(){
      return this.get('vertical') ? this.get('scrollToY') : this.get('scrollToX');
    },
    set(key, value){
      deprecate('Using the `scrollTo` property directly has been deprecated, please prefer being explicit by using `scrollToX` and `scrollToY`.');
      const prop = this.get('vertical') ? 'scrollToY' : 'scrollToX';
      this.set(prop, value);
      return value;
    }
  }),

  /**
   * Position in pixels for which to scroll horizontal scrollbar.
   *
   * @property scrollToX
   * @public
   * @type Number
   */
  scrollToX: 0,
  /**
   * Position in pixels for which to scroll vertical scrollbar.
   *
   * @property scrollToY
   * @public
   * @type Number
   */
  scrollToY: 0,

  /**
   * Local reference the horizontal scrollbar.
   *
   * @property horizontalScrollbar
   * @private
   */
  horizontalScrollbar: null,
  /**
   * Local reference the vertical scrollbar.
   *
   * @property verticalScrollbar
   * @private
   */
  verticalScrollbar: null,

  didReceiveAttrs() {
    const horizontal = this.get('horizontal');
    const vertical = this.get('horizontal');
    // Keep backwards compatible functionality wherein vertical is default when neither vertical or horizontal are explicitly set
    if (!horizontal && !isPresent(vertical)) {
      this.set('vertical', true);
    }
  },

  didInsertElement() {
    this._super(...arguments);
    this.setupElements();
    scheduleOnce('afterRender', this, this.createScrollbarAndShowIfNecessary);
    this.addEventListener(document.body, 'mouseup', (e) => this.endDrag(e));
    this.addEventListener(document.body, 'mousemove', (e) => {
      throttle(this, this.updateMouseOffset, e, THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS);
    });
    this.setupResize();
  },

  willDestroyElement() {
    this._super(...arguments);

    this.$().off('transitionend webkitTransitionEnd', this._resizeHandler);
  },

  /**
   * Inidcates that the horizontal scrollbar is dragging at this moment in time.
   * @property isHorizontalDragging
   * @private
   */
  isHorizontalDragging: false,
  /**
   * Inidcates that the vertical scrollbar is dragging at this moment in time.
   * @property isVerticalDragging
   * @private
   */
  isVerticalDragging: false,
  /**
   * Size in pixels of the handle within the horizontal scrollbar.
   * Determined by a ration between the scroll content and the scroll viewport
   *
   * @property horizontalHandleSize
   * @private
   */
  horizontalHandleSize: null,
  /**
   * Size in pixels of the handle within the vertical scrollbar.
   * Determined by a ration between the scroll content and the scroll viewport
   *
   * @property verticalHandleSize
   * @private
   */
  verticalHandleSize: null,
  /**
   * Amount in pixels offset from the anchor (leftmost point of horizontal scrollbar)
   *
   * @property horizontalHandleOffset
   * @private
   */
  horizontalHandleOffset: 0,
  /**
   * Amount in pixels offset from the anchor (topmost point of vertical scrollbar)
   *
   * @property verticalHandleOffest
   * @private
   */
  verticalHandleOffest: 0,
  /**
   *
   * @property dragOffset
   * @private
   */
  dragOffset: 0,
  /**
   * Horizontal offset in pixels from the boundary of the scrollable container to the mouse.
   *
   * @property horizontalMouseOffset
   * @private
   * @type Number
   */
  horizontalMouseOffset: 0,
  /**
   * Vertical offset in pixels from the boundary of the scrollable container to the mouse.
   *
   * @property verticalMouseOffset
   * @private
   * @type Number
   */
  verticalMouseOffset: 0,

  scrollContentSize(sizeAttr) {
    return this._scrollContentElement[sizeAttr]();
  },

  contentSize(sizeAttr) {
    return this._contentElement[sizeAttr]();
  },

  setupElements() {
    this._scrollContentElement = this.$(`${scrollContentSelector}`);
    this._contentElement = this.$(`${contentSelector}:first`);
  },

  measureScrollbar() {

    /**
     * Calculate scrollbar width
     *
     * Original function by Jonathan Sharp:
     * http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php
     * Updated to work in Chrome v25.
     */
    function scrollbarWidth() {
      // Append a temporary scrolling element to the DOM, then measure
      // the difference between between its outer and inner elements.
      var tempEl = $('

');
      $('body').append(tempEl);
      var width = $(tempEl).innerWidth();
      var widthMinusScrollbars = $('div', tempEl).innerWidth();
      tempEl.remove();

      // On OS X if the scrollbar is set to auto hide it will have zero width. On webkit we can still
      // hide it using ::-webkit-scrollbar { width:0; height:0; } but there is no moz equivalent. So we're
      // forced to sniff Firefox and return a hard-coded scrollbar width. I know, I know...

      // https://github.com/alphasights/ember-scrollable/issues/34
      // if (width === widthMinusScrollbars && navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
      //  return 17;
      // }

      return (width - widthMinusScrollbars);
    }

    return scrollbarWidth();

  },

  /**
   * Used to create/reset scrollbar(s) if they are necessary
   *
   * @method createScrollbarAndShowIfNecessary
   */
  createScrollbarAndShowIfNecessary() {
    this.createScrollbar().map((scrollbar) => {
      this.checkScrolledToBottom(scrollbar);
      if (scrollbar.isNecessary) {
        this.showScrollbar();
      }
    });
  },

  _resizeHandler(){
    debounce(this, this.resizeScrollbar, 16);
  },

  setupResize() {
    this.addEventListener(window, 'resize', this._resizeHandler, true);
  },

  resizeScrollContent() {
    const width = this.$().width();
    const height = this.$().height();
    const scrollbarThickness = this.measureScrollbar();

    const hasHorizontal = this.get('horizontal');
    const hasVertical = this.get('vertical');

    if (hasHorizontal && hasVertical) {
      this.set('scrollContentWidth', width + scrollbarThickness);
      this.set('scrollContentHeight', height + scrollbarThickness);
    } else {
      if (hasHorizontal) {
        this.set('scrollContentWidth', width);
        this.set('scrollContentHeight', height + scrollbarThickness);
        this._contentElement.height(height);
      } else {
        this.set('scrollContentWidth', width + scrollbarThickness);
        this.set('scrollContentHeight', height);
      }
    }
  },

  /**
   * Creates the corresponding scrollbar(s) from the configured `vertical` and `horizontal` properties
   *
   * @method createScrollbar
   * @return {Array} Scrollbar(s) that were created
   */
  createScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    const scrollbars = [];

    this.resizeScrollContent();

    if (this.get('vertical')) {
      const verticalScrollbar = new Vertical({
        scrollbarElement: this.$(`${scrollbarSelector}.vertical`),
        contentElement: this._contentElement
      });
      this.set('verticalScrollbar', verticalScrollbar);
      this.updateScrollbarAndSetupProperties(0, 'vertical');
      scrollbars.push(verticalScrollbar);
    }
    if (this.get('horizontal')) {
      const horizontalScrollbar = new Horizontal({
        scrollbarElement: this.$(`${scrollbarSelector}.horizontal`),
        contentElement: this._contentElement
      });
      this.set('horizontalScrollbar', horizontalScrollbar);
      this.updateScrollbarAndSetupProperties(0, 'horizontal');
      scrollbars.push(horizontalScrollbar);
    }
    return scrollbars;
  },

  /**
   * Show the scrollbar(s) when the user moves within the scroll viewport
   *
   * @method mouseMove
   * @private
   */
  mouseMove(){
    if (this.get('autoHide')) {
      throttle(this, this.showScrollbar, THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS);
    }
  },

  /**
   * Callback for the mouse move event. Update the mouse offsets given the new mouse position.
   *
   * @method updateMouseOffset
   * @param e
   * @private
   */
  updateMouseOffset(e){
    const {pageX, pageY} = e;
    this.set('horizontalMouseOffset', pageX);
    this.set('verticalMouseOffset', pageY);
  },

  /**
   * Called on mouse up to indicate dragging is over.
   *
   * @method endDrag
   * @param e
   * @private
   */

  endDrag(e) {
    /* jshint unused:vars */
    this.set('isVerticalDragging', false);
    this.set('isHorizontalDragging', false);
  },

  /**
   * Calculates and setups the correct handle position using the scrollbar offset and size
   *
   * @method updateScrollbarAndSetupProperties
   * @param scrollOffset
   * @param scrollbarDirection
   * @private
   */
  updateScrollbarAndSetupProperties(scrollOffset, scrollbarDirection) {
    const {handleOffset, handleSize} = this.get(`${scrollbarDirection}Scrollbar`).getHandlePositionAndSize(scrollOffset);
    this.set(`${scrollbarDirection}HandleOffset`, handleOffset);
    this.set(`${scrollbarDirection}HandleSize`, handleSize);
  },

  /**
   * Callback for the scroll event emitted by the container of the content that can scroll.
   * Here we update the scrollbar to reflect the scrolled position.
   *
   * @method scrolled
   * @param event
   * @param scrollOffset
   * @param scrollDirection 'vertical' or 'horizontal'
   * @private
   */
  scrolled(event, scrollOffset, scrollDirection) {
    this.updateScrollbarAndSetupProperties(scrollOffset, scrollDirection);
    this.showScrollbar();

    this.checkScrolledToBottom(this.get(`${scrollDirection}Scrollbar`), scrollOffset);

    this.sendScroll(event, scrollOffset);
  },

  checkScrolledToBottom(scrollbar, scrollOffset) {
    let scrollBuffer = this.get('scrollBuffer');

    if (scrollbar.isScrolledToBottom(scrollBuffer, scrollOffset)) {
      debounce(this, this.sendScrolledToBottom, 100);
    }
  },

  sendScrolledToBottom() {
    this.sendAction('onScrolledToBottom');
  },

  sendScroll(event, scrollOffset) {
    if (this.get('onScroll')) {
      this.sendAction('onScroll', scrollOffset, event);
    }
  },

  resizeScrollbar() {
    this.createScrollbarAndShowIfNecessary();
  },

  showScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    this.set('showHandle', true);

    if (!this.get('autoHide')) {
      return;
    }

    debounce(this, this.hideScrollbar, hideDelay);
  },

  hideScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    this.set('showHandle', false);
  },

  /**
   * Sets scrollTo{X,Y} by using the ratio of offset to content size.
   * Called when the handle in ember-scrollbar is dragged.
   *
   * @method updateScrollToProperty
   * @param scrollProp {String} String indicating the scrollTo attribute to be updated ('scrollToX'|'scrollToY')
   * @param dragPerc {Number} A Number from 0 - 1 indicating the position of the handle as percentage of the scrollbar
   * @param sizeAttr {String} String indicating the attribute used to get to the size of the content ('height'|'width')
   * @private
   */
  updateScrollToProperty(scrollProp, dragPerc, sizeAttr) {
    const srcollTo = dragPerc * this.contentSize(sizeAttr);
    this.set(scrollProp, srcollTo);
  },

  /**
   * Sets is{Horizontal,Vertical}Dragging to true or false when the handle starts or ends dragging
   *
   * @method toggleDraggingBoundary
   * @param isDraggingProp 'isHorizontalDragging' or 'isVerticalDragging'
   * @param startOrEnd true if starting to drag, false if ending
   * @private
   */
  toggleDraggingBoundary(isDraggingProp, startOrEnd) {
    this.set(isDraggingProp, startOrEnd);
  },

  /**
   * Jumps a page because user clicked on scroll bar not scroll bar handle.
   *
   * @method jumpScroll
   * @param jumpPositive if true the user clicked between the handle and the end, if false, the user clicked between the
   *  anchor and the handle
   * @param scrollToProp 'scrollToX' or 'scrollToY'
   * @param sizeAttr 'width' or 'height'
   * @private
   */
  jumpScroll(jumpPositive, scrollToProp, sizeAttr) {
    const scrollOffset = this.get(scrollToProp);
    let jumpAmt = PAGE_JUMP_MULTIPLE * this.scrollContentSize(sizeAttr);
    let scrollPos = jumpPositive ? scrollOffset - jumpAmt : scrollOffset + jumpAmt;
    this.set(scrollToProp, scrollPos);
  },

  actions: {

    /**
     * Update action should be called when size of the scroll area changes
     */
    recalculate() {
      this.resizeScrollbar();
    },

    /**
     * Can be called when scrollbars changes as a result of value change,
     *
     * for example
     * ```
     * {{#as-scrollable as |scrollbar|}}
     *   {{#each (compute scrollbar.update rows) as |row|}}
     *     {{row.title}}
     *   {{/each}}
     * {{/as-scrollable}}
     * ```
     */
    update(value) {
      scheduleOnce('afterRender', this, this.resizeScrollbar);
      return value;
    },

    /**
     * Scroll Top action should be called when when the scroll area should be scrolled top manually
     */
    scrollTop() {
      this.set('scrollToY', 0);
    },
    scrolled(){
      scheduleOnce('afterRender', this, 'scrolled', ...arguments);
    },
    horizontalDrag(dragPerc) {
      scheduleOnce('afterRender', this, 'updateScrollToProperty', 'scrollToX', dragPerc, 'width');
    },
    verticalDrag(dragPerc) {
      scheduleOnce('afterRender', this, 'updateScrollToProperty', 'scrollToY', dragPerc, 'height');
    },
    horizontalJumpTo(jumpPositive) {
      this.jumpScroll(jumpPositive, 'scrollToX', 'width');
    },
    verticalJumpTo(jumpPositive) {
      this.jumpScroll(jumpPositive, 'scrollToY', 'height');
    },
    horizontalDragBoundary(isStart) {
      this.toggleDraggingBoundary('isHorizontalDragging', isStart);
    },
    verticalBoundaryEvent(isStart) {
      this.toggleDraggingBoundary('isVerticalDragging', isStart);
    }
  }
});
<commit_msg>Adding event listeners to window object<commit_after>import Ember from 'ember';
import InboundActionsMixin from 'ember-component-inbound-actions/inbound-actions';
import DomMixin from 'ember-lifeline/mixins/dom';
import layout from '../templates/components/ember-scrollable';
import {Horizontal, Vertical} from '../classes/scrollable';

const {
  computed,
  deprecate,
  isPresent,
  run: {
    scheduleOnce,
    debounce,
    throttle
  },
  $
} = Ember;

const hideDelay = Ember.testing ? 16 : 1000;
const PAGE_JUMP_MULTIPLE = 7 / 8;

const THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS = 1; // 60 fps -> 1 sec / 60 = 16ms

const scrollbarSelector = '.tse-scrollbar';
const scrollContentSelector = '.tse-scroll-content';
const contentSelector = '.tse-content';

export default Ember.Component.extend(InboundActionsMixin, DomMixin, {
  layout,
  classNameBindings: [':ember-scrollable', ':tse-scrollable', 'horizontal', 'vertical'],

  /**
   * If true, a scrollbar will be shown horizontally
   *
   * @property horizontal
   * @public
   * @type Boolean
   * @default false
   */
  horizontal: null,

  /**
   * If true, a scrollbar will be shown vertically
   *
   * @property vertical
   * @public
   * @type Boolean
   */
  vertical: null,
  /**
   * Indicates whether the scrollbar should auto hide after a given period of time (see hideDelay),
   * or remain persitent alongside the content to be scrolled.
   *
   * @property autoHide
   * @public
   * @type Boolean
   * @default true
   */
  autoHide: true,
  scrollBuffer: 50,
  /**
   * Number indicating offset from anchor point (top for vertical, left for horizontal) where the scroll handle
   * should be rendered.
   *
   * @property scrollTo
   * @public
   * @type Number
   */
  scrollTo: computed('vertical', {
    get(){
      return this.get('vertical') ? this.get('scrollToY') : this.get('scrollToX');
    },
    set(key, value){
      deprecate('Using the `scrollTo` property directly has been deprecated, please prefer being explicit by using `scrollToX` and `scrollToY`.');
      const prop = this.get('vertical') ? 'scrollToY' : 'scrollToX';
      this.set(prop, value);
      return value;
    }
  }),

  /**
   * Position in pixels for which to scroll horizontal scrollbar.
   *
   * @property scrollToX
   * @public
   * @type Number
   */
  scrollToX: 0,
  /**
   * Position in pixels for which to scroll vertical scrollbar.
   *
   * @property scrollToY
   * @public
   * @type Number
   */
  scrollToY: 0,

  /**
   * Local reference the horizontal scrollbar.
   *
   * @property horizontalScrollbar
   * @private
   */
  horizontalScrollbar: null,
  /**
   * Local reference the vertical scrollbar.
   *
   * @property verticalScrollbar
   * @private
   */
  verticalScrollbar: null,

  didReceiveAttrs() {
    const horizontal = this.get('horizontal');
    const vertical = this.get('horizontal');
    // Keep backwards compatible functionality wherein vertical is default when neither vertical or horizontal are explicitly set
    if (!horizontal && !isPresent(vertical)) {
      this.set('vertical', true);
    }
  },

  didInsertElement() {
    this._super(...arguments);
    this.setupElements();
    scheduleOnce('afterRender', this, this.createScrollbarAndShowIfNecessary);
    this.addEventListener(window, 'mouseup', (e) => this.endDrag(e));
    this.addEventListener(window, 'mousemove', (e) => {
      throttle(this, this.updateMouseOffset, e, THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS);
    });
    this.setupResize();
  },

  willDestroyElement() {
    this._super(...arguments);

    this.$().off('transitionend webkitTransitionEnd', this._resizeHandler);
  },

  /**
   * Inidcates that the horizontal scrollbar is dragging at this moment in time.
   * @property isHorizontalDragging
   * @private
   */
  isHorizontalDragging: false,
  /**
   * Inidcates that the vertical scrollbar is dragging at this moment in time.
   * @property isVerticalDragging
   * @private
   */
  isVerticalDragging: false,
  /**
   * Size in pixels of the handle within the horizontal scrollbar.
   * Determined by a ration between the scroll content and the scroll viewport
   *
   * @property horizontalHandleSize
   * @private
   */
  horizontalHandleSize: null,
  /**
   * Size in pixels of the handle within the vertical scrollbar.
   * Determined by a ration between the scroll content and the scroll viewport
   *
   * @property verticalHandleSize
   * @private
   */
  verticalHandleSize: null,
  /**
   * Amount in pixels offset from the anchor (leftmost point of horizontal scrollbar)
   *
   * @property horizontalHandleOffset
   * @private
   */
  horizontalHandleOffset: 0,
  /**
   * Amount in pixels offset from the anchor (topmost point of vertical scrollbar)
   *
   * @property verticalHandleOffest
   * @private
   */
  verticalHandleOffest: 0,
  /**
   *
   * @property dragOffset
   * @private
   */
  dragOffset: 0,
  /**
   * Horizontal offset in pixels from the boundary of the scrollable container to the mouse.
   *
   * @property horizontalMouseOffset
   * @private
   * @type Number
   */
  horizontalMouseOffset: 0,
  /**
   * Vertical offset in pixels from the boundary of the scrollable container to the mouse.
   *
   * @property verticalMouseOffset
   * @private
   * @type Number
   */
  verticalMouseOffset: 0,

  scrollContentSize(sizeAttr) {
    return this._scrollContentElement[sizeAttr]();
  },

  contentSize(sizeAttr) {
    return this._contentElement[sizeAttr]();
  },

  setupElements() {
    this._scrollContentElement = this.$(`${scrollContentSelector}`);
    this._contentElement = this.$(`${contentSelector}:first`);
  },

  measureScrollbar() {

    /**
     * Calculate scrollbar width
     *
     * Original function by Jonathan Sharp:
     * http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php
     * Updated to work in Chrome v25.
     */
    function scrollbarWidth() {
      // Append a temporary scrolling element to the DOM, then measure
      // the difference between between its outer and inner elements.
      var tempEl = $('

');
      $('body').append(tempEl);
      var width = $(tempEl).innerWidth();
      var widthMinusScrollbars = $('div', tempEl).innerWidth();
      tempEl.remove();

      // On OS X if the scrollbar is set to auto hide it will have zero width. On webkit we can still
      // hide it using ::-webkit-scrollbar { width:0; height:0; } but there is no moz equivalent. So we're
      // forced to sniff Firefox and return a hard-coded scrollbar width. I know, I know...

      // https://github.com/alphasights/ember-scrollable/issues/34
      // if (width === widthMinusScrollbars && navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
      //  return 17;
      // }

      return (width - widthMinusScrollbars);
    }

    return scrollbarWidth();

  },

  /**
   * Used to create/reset scrollbar(s) if they are necessary
   *
   * @method createScrollbarAndShowIfNecessary
   */
  createScrollbarAndShowIfNecessary() {
    this.createScrollbar().map((scrollbar) => {
      this.checkScrolledToBottom(scrollbar);
      if (scrollbar.isNecessary) {
        this.showScrollbar();
      }
    });
  },

  _resizeHandler(){
    debounce(this, this.resizeScrollbar, 16);
  },

  setupResize() {
    this.addEventListener(window, 'resize', this._resizeHandler, true);
  },

  resizeScrollContent() {
    const width = this.$().width();
    const height = this.$().height();
    const scrollbarThickness = this.measureScrollbar();

    const hasHorizontal = this.get('horizontal');
    const hasVertical = this.get('vertical');

    if (hasHorizontal && hasVertical) {
      this.set('scrollContentWidth', width + scrollbarThickness);
      this.set('scrollContentHeight', height + scrollbarThickness);
    } else {
      if (hasHorizontal) {
        this.set('scrollContentWidth', width);
        this.set('scrollContentHeight', height + scrollbarThickness);
        this._contentElement.height(height);
      } else {
        this.set('scrollContentWidth', width + scrollbarThickness);
        this.set('scrollContentHeight', height);
      }
    }
  },

  /**
   * Creates the corresponding scrollbar(s) from the configured `vertical` and `horizontal` properties
   *
   * @method createScrollbar
   * @return {Array} Scrollbar(s) that were created
   */
  createScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    const scrollbars = [];

    this.resizeScrollContent();

    if (this.get('vertical')) {
      const verticalScrollbar = new Vertical({
        scrollbarElement: this.$(`${scrollbarSelector}.vertical`),
        contentElement: this._contentElement
      });
      this.set('verticalScrollbar', verticalScrollbar);
      this.updateScrollbarAndSetupProperties(0, 'vertical');
      scrollbars.push(verticalScrollbar);
    }
    if (this.get('horizontal')) {
      const horizontalScrollbar = new Horizontal({
        scrollbarElement: this.$(`${scrollbarSelector}.horizontal`),
        contentElement: this._contentElement
      });
      this.set('horizontalScrollbar', horizontalScrollbar);
      this.updateScrollbarAndSetupProperties(0, 'horizontal');
      scrollbars.push(horizontalScrollbar);
    }
    return scrollbars;
  },

  /**
   * Show the scrollbar(s) when the user moves within the scroll viewport
   *
   * @method mouseMove
   * @private
   */
  mouseMove(){
    if (this.get('autoHide')) {
      throttle(this, this.showScrollbar, THROTTLE_TIME_LESS_THAN_60_FPS_IN_MS);
    }
  },

  /**
   * Callback for the mouse move event. Update the mouse offsets given the new mouse position.
   *
   * @method updateMouseOffset
   * @param e
   * @private
   */
  updateMouseOffset(e){
    const {pageX, pageY} = e;
    this.set('horizontalMouseOffset', pageX);
    this.set('verticalMouseOffset', pageY);
  },

  /**
   * Called on mouse up to indicate dragging is over.
   *
   * @method endDrag
   * @param e
   * @private
   */

  endDrag(e) {
    /* jshint unused:vars */
    this.set('isVerticalDragging', false);
    this.set('isHorizontalDragging', false);
  },

  /**
   * Calculates and setups the correct handle position using the scrollbar offset and size
   *
   * @method updateScrollbarAndSetupProperties
   * @param scrollOffset
   * @param scrollbarDirection
   * @private
   */
  updateScrollbarAndSetupProperties(scrollOffset, scrollbarDirection) {
    const {handleOffset, handleSize} = this.get(`${scrollbarDirection}Scrollbar`).getHandlePositionAndSize(scrollOffset);
    this.set(`${scrollbarDirection}HandleOffset`, handleOffset);
    this.set(`${scrollbarDirection}HandleSize`, handleSize);
  },

  /**
   * Callback for the scroll event emitted by the container of the content that can scroll.
   * Here we update the scrollbar to reflect the scrolled position.
   *
   * @method scrolled
   * @param event
   * @param scrollOffset
   * @param scrollDirection 'vertical' or 'horizontal'
   * @private
   */
  scrolled(event, scrollOffset, scrollDirection) {
    this.updateScrollbarAndSetupProperties(scrollOffset, scrollDirection);
    this.showScrollbar();

    this.checkScrolledToBottom(this.get(`${scrollDirection}Scrollbar`), scrollOffset);

    this.sendScroll(event, scrollOffset);
  },

  checkScrolledToBottom(scrollbar, scrollOffset) {
    let scrollBuffer = this.get('scrollBuffer');

    if (scrollbar.isScrolledToBottom(scrollBuffer, scrollOffset)) {
      debounce(this, this.sendScrolledToBottom, 100);
    }
  },

  sendScrolledToBottom() {
    this.sendAction('onScrolledToBottom');
  },

  sendScroll(event, scrollOffset) {
    if (this.get('onScroll')) {
      this.sendAction('onScroll', scrollOffset, event);
    }
  },

  resizeScrollbar() {
    this.createScrollbarAndShowIfNecessary();
  },

  showScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    this.set('showHandle', true);

    if (!this.get('autoHide')) {
      return;
    }

    debounce(this, this.hideScrollbar, hideDelay);
  },

  hideScrollbar() {
    if (this.get('isDestroyed')) {
      return;
    }
    this.set('showHandle', false);
  },

  /**
   * Sets scrollTo{X,Y} by using the ratio of offset to content size.
   * Called when the handle in ember-scrollbar is dragged.
   *
   * @method updateScrollToProperty
   * @param scrollProp {String} String indicating the scrollTo attribute to be updated ('scrollToX'|'scrollToY')
   * @param dragPerc {Number} A Number from 0 - 1 indicating the position of the handle as percentage of the scrollbar
   * @param sizeAttr {String} String indicating the attribute used to get to the size of the content ('height'|'width')
   * @private
   */
  updateScrollToProperty(scrollProp, dragPerc, sizeAttr) {
    const srcollTo = dragPerc * this.contentSize(sizeAttr);
    this.set(scrollProp, srcollTo);
  },

  /**
   * Sets is{Horizontal,Vertical}Dragging to true or false when the handle starts or ends dragging
   *
   * @method toggleDraggingBoundary
   * @param isDraggingProp 'isHorizontalDragging' or 'isVerticalDragging'
   * @param startOrEnd true if starting to drag, false if ending
   * @private
   */
  toggleDraggingBoundary(isDraggingProp, startOrEnd) {
    this.set(isDraggingProp, startOrEnd);
  },

  /**
   * Jumps a page because user clicked on scroll bar not scroll bar handle.
   *
   * @method jumpScroll
   * @param jumpPositive if true the user clicked between the handle and the end, if false, the user clicked between the
   *  anchor and the handle
   * @param scrollToProp 'scrollToX' or 'scrollToY'
   * @param sizeAttr 'width' or 'height'
   * @private
   */
  jumpScroll(jumpPositive, scrollToProp, sizeAttr) {
    const scrollOffset = this.get(scrollToProp);
    let jumpAmt = PAGE_JUMP_MULTIPLE * this.scrollContentSize(sizeAttr);
    let scrollPos = jumpPositive ? scrollOffset - jumpAmt : scrollOffset + jumpAmt;
    this.set(scrollToProp, scrollPos);
  },

  actions: {

    /**
     * Update action should be called when size of the scroll area changes
     */
    recalculate() {
      this.resizeScrollbar();
    },

    /**
     * Can be called when scrollbars changes as a result of value change,
     *
     * for example
     * ```
     * {{#as-scrollable as |scrollbar|}}
     *   {{#each (compute scrollbar.update rows) as |row|}}
     *     {{row.title}}
     *   {{/each}}
     * {{/as-scrollable}}
     * ```
     */
    update(value) {
      scheduleOnce('afterRender', this, this.resizeScrollbar);
      return value;
    },

    /**
     * Scroll Top action should be called when when the scroll area should be scrolled top manually
     */
    scrollTop() {
      this.set('scrollToY', 0);
    },
    scrolled(){
      scheduleOnce('afterRender', this, 'scrolled', ...arguments);
    },
    horizontalDrag(dragPerc) {
      scheduleOnce('afterRender', this, 'updateScrollToProperty', 'scrollToX', dragPerc, 'width');
    },
    verticalDrag(dragPerc) {
      scheduleOnce('afterRender', this, 'updateScrollToProperty', 'scrollToY', dragPerc, 'height');
    },
    horizontalJumpTo(jumpPositive) {
      this.jumpScroll(jumpPositive, 'scrollToX', 'width');
    },
    verticalJumpTo(jumpPositive) {
      this.jumpScroll(jumpPositive, 'scrollToY', 'height');
    },
    horizontalDragBoundary(isStart) {
      this.toggleDraggingBoundary('isHorizontalDragging', isStart);
    },
    verticalBoundaryEvent(isStart) {
      this.toggleDraggingBoundary('isVerticalDragging', isStart);
    }
  }
});
<|endoftext|>


<commit_before>define(
[],
function() {
  var config = {
    mapServices:{
       vector: "http://gis.wirapids.org/arcgis/rest/services/VectorBasemapHybrid/MapServer",
       dynamic: "http://gis.wirapids.org/arcgis/rest/services/DynamicBasemap/MapServer",
       aerial: "http://gis.wirapids.org/arcgis/rest/services/AerialBasemap/MapServer"
    },
    helperServices: {
       geometry:"http://gis.wirapids.org/arcgis/rest/services/Utilities/Geometry/GeometryServer",
       printTask: "http://gis.wirapids.org/arcgis/rest/services/ExportWebMap/GPServer/Export%20Web%20Map"
    }
};

  // could use a has() test to optionally populate some global
  // property so that the stuff defined is in some global identifier
  //
  // instead, just populate a global, will need to remove the next line when
  // when we remove support for loading modules with dojo.require
  // which will be when we move to Dojo 2.0
  commonConfig = config;
  // instead of using a global, this should probably be added to some namespace...
  // do the templates have a common namespace that they use?

  return config;
});
<commit_msg>add additional configurable items<commit_after>define(
[],
function() {
  var config = {
    mapServices:{
       vector: "http://gis.wirapids.org/arcgis/rest/services/VectorBasemapHybrid/MapServer",
       dynamic: "http://gis.wirapids.org/arcgis/rest/services/DynamicBasemap/MapServer",
       aerial: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/"
    },
    helperServices: {
       geometry:"http://gis.wirapids.org/arcgis/rest/services/Utilities/Geometry/GeometryServer",
       print: "http://gis.wirapids.org/arcgis/rest/services/ExportWebMap/GPServer/Export%20Web%20Map"
    },
	utilityLayerID:[-1],
	parcelLayerID: [27],
	csvUrl: "http://gis.wirapids.org/CityViewer/outputs/parcelInfo.csv",
	mailLink: "https://gis.wirapids.org/CityViewer/outputs/",
	identifyLayerAdditional:[]

};

  // could use a has() test to optionally populate some global
  // property so that the stuff defined is in some global identifier
  //
  // instead, just populate a global, will need to remove the next line when
  // when we remove support for loading modules with dojo.require
  // which will be when we move to Dojo 2.0
  commonConfig = config;
  // instead of using a global, this should probably be added to some namespace...
  // do the templates have a common namespace that they use?

  return config;
});
<|endoftext|>


<commit_before>
function uploadResponse(target, response)
{
	if (target === undefined)
	{
		return;
	}

    if (response == "success")
    {
        logourl = chrome.extension.getURL("logo_16_success.png");
        target.src = logourl;
    }
    else if  (response == "failure")
    {
        logourl = chrome.extension.getURL("logo_16_failure.png");
        target.src = logourl;
    }
    else
    {
        logourl = chrome.extension.getURL("logo_16.png");
        target.src = logourl;
    }
}

function sendURL(e)
{
    logourl = chrome.extension.getURL("logo_16_pending.png");
    e.target.src = logourl;

	doSendURL(e.target, e.target.parentNode.url);
}

function doSendURL(target, url)
{
    if (getURLType(url) != "torrent")
    {
        console.log("Sending URL: " + url);
        chrome.runtime.sendMessage({type: "uploadURL", value: url, torrent: undefined}, uploadResponse.bind(undefined, target));
    }
    else
    {
        // Need to get the torrent file in this context in the contentscript, will probably fail otherwise
        var http = new XMLHttpRequest();

		if (url.startsWith("torrent:"))
		{
			hurl = url.substring(8);
		}
        else
        {
            hurl = url;
        }

        http.open(getURLMethod(hurl), hurl, true);
        http.responseType = 'blob';
        http.onreadystatechange = function()
        {
            if(http.readyState == 4)
            {
                if (http.status != 200)
                {
                    alert("Downloading torrent failed: " + http.status + "!");
                }
                else
                {
                    if (http.response.type == "text/html")
                    {
                        var reader = new window.FileReader();
                        reader.onloadend = function()
                        {
                            if (reader.result.length > 400)
                            {
                                alert("Got unexpected long HTML response (" + reader.result.length + " bytes)!");
                            }
                            else
                            {
                                alert("Got unexpected HTML response: " + reader.result + "!");
                            }
                        };
                        reader.readAsText(http.response);
                    }
                    else
                    {
                        var reader = new window.FileReader();
                        reader.onloadend = function()
                        {
                            chrome.runtime.sendMessage({type: "uploadURL", value: url, torrent: reader.result},
                                                       uploadResponse.bind(undefined, target));
                        };
                        reader.readAsDataURL(http.response);
                    }
               }
            };
         };

        http.send();
     }
}

function do_add_buttons()
{
    var links = document.getElementsByTagName('a');

    // General pattern-match handler
    for(var i=0; i<links.length; i++)
    {
        l = links[i];
        url = l.href;

        if (getURLType(url) != "No")
        {
            do_add_single_button(l, url);
        }
    }
 }

// Site-specific startup

// TVTorrents.com
// Need to run as injected code, needs access to scripts...
if (document.URL.indexOf("tvtorrents.com") > -1)
{
    var script = document.createElement('script');
    script.src = chrome.extension.getURL('injector.js');
    script.onload = function() {
        this.parentNode.removeChild(this);
    };
    (document.head||document.documentElement).appendChild(script);
}

// Generic startup

var have_localpatterns = false;
if (have_localpatterns)
{
    do_add_buttons();
}

// Get local patterns
chrome.runtime.sendMessage({type: "getLocalPatterns"}, function (response)
    {
        url_patterns = url_patterns.concat(response.localpatterns);

        have_localpatterns = true;

        do_add_buttons();
    });

////////////////////////////////////
// Message handling
// From extension
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    //console.log(sender.tab ?
    //            "from a content script:" + sender.tab.url :
    //            "from the extension");
    if (request.type == "readdButtons")
    {
        do_add_buttons();
        window.postMessage({type: 'readdButtons'}, '*' /* targetOrigin: any */);
    }
    else if (request.type == "addPattern")
    {
        if (request.pattern instanceof Array)
        {
            url_patterns = url_patterns.concat(request.pattern);
        }
        else
        {
            url_patterns.push(request.pattern);
        }

        do_add_buttons();
    }
    else if (request.type == "sendURL")
    {
        doSendURL(undefined, request.url);
    }
    else
    {
        console.log("Got unknown message " + request);
    }
});

// From injected scripts
window.addEventListener('message', function(request) {
    if (! request.data.hasOwnProperty("type"))
    {
        return;
    }

    if (request.data.type == "addButton")
    {
        var elem = document.getElementById(request.data.id);
        do_add_single_button(elem, request.data.url);
    }
    else
    {
        console.log("Got unknown message " + request.toString());
    }
});<commit_msg>Added ome better error handling<commit_after>
function uploadResponse(target, response)
{
	if (target === undefined)
	{
		return;
	}

    if (response == "success")
    {
        logourl = chrome.extension.getURL("logo_16_success.png");
        target.src = logourl;
    }
    else if  (response == "failure")
    {
        logourl = chrome.extension.getURL("logo_16_failure.png");
        target.src = logourl;
    }
    else
    {
        logourl = chrome.extension.getURL("logo_16.png");
        target.src = logourl;
    }
}

function sendURL(e)
{
    logourl = chrome.extension.getURL("logo_16_pending.png");
    e.target.src = logourl;

	doSendURL(e.target, e.target.parentNode.url);
}

function doSendURL(target, url)
{
    if (getURLType(url) != "torrent")
    {
        console.log("Sending URL: " + url);
        chrome.runtime.sendMessage({type: "uploadURL", value: url, torrent: undefined}, uploadResponse.bind(undefined, target));
    }
    else
    {
        // Need to get the torrent file in this context in the contentscript, will probably fail otherwise
        var http = new XMLHttpRequest();

		if (url.startsWith("torrent:"))
		{
			hurl = url.substring(8);
		}
        else
        {
            hurl = url;
        }

        http.open(getURLMethod(hurl), hurl, true);
        http.responseType = 'blob';
        http.onreadystatechange = function()
        {
            if(http.readyState == 4)
            {
                if (http.status != 200)
                {
                    alert("Downloading torrent failed: " + http.status + "!");
                    uploadResponse(target, "failure");
                }
                else
                {
                    if (http.response.type == "text/html")
                    {
                        var reader = new window.FileReader();
                        reader.onloadend = function()
                        {
                            if (reader.result.length > 400)
                            {
                                alert("Got unexpected long HTML response (" + reader.result.length + " bytes)!");
                            }
                            else
                            {
                                alert("Got unexpected HTML response: " + reader.result + "!");
                            }
                        };
                        reader.readAsText(http.response);
                        uploadResponse(target, "failure");
                    }
                    else
                    {
                        var reader = new window.FileReader();
                        reader.onloadend = function()
                        {
                            chrome.runtime.sendMessage({type: "uploadURL", value: url, torrent: reader.result},
                                                       uploadResponse.bind(undefined, target));
                        };
                        reader.readAsDataURL(http.response);
                    }
               }
            };
         };

        http.send();
     }
}

function do_add_buttons()
{
    var links = document.getElementsByTagName('a');

    // General pattern-match handler
    for(var i=0; i<links.length; i++)
    {
        l = links[i];
        url = l.href;

        if (getURLType(url) != "No")
        {
            do_add_single_button(l, url);
        }
    }
 }

// Site-specific startup

// TVTorrents.com
// Need to run as injected code, needs access to scripts...
if (document.URL.indexOf("tvtorrents.com") > -1)
{
    var script = document.createElement('script');
    script.src = chrome.extension.getURL('injector.js');
    script.onload = function() {
        this.parentNode.removeChild(this);
    };
    (document.head||document.documentElement).appendChild(script);
}

// Generic startup

var have_localpatterns = false;
if (have_localpatterns)
{
    do_add_buttons();
}

// Get local patterns
chrome.runtime.sendMessage({type: "getLocalPatterns"}, function (response)
    {
        url_patterns = url_patterns.concat(response.localpatterns);

        have_localpatterns = true;

        do_add_buttons();
    });

////////////////////////////////////
// Message handling
// From extension
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    //console.log(sender.tab ?
    //            "from a content script:" + sender.tab.url :
    //            "from the extension");
    if (request.type == "readdButtons")
    {
        do_add_buttons();
        window.postMessage({type: 'readdButtons'}, '*' /* targetOrigin: any */);
    }
    else if (request.type == "addPattern")
    {
        if (request.pattern instanceof Array)
        {
            url_patterns = url_patterns.concat(request.pattern);
        }
        else
        {
            url_patterns.push(request.pattern);
        }

        do_add_buttons();
    }
    else if (request.type == "sendURL")
    {
        doSendURL(undefined, request.url);
    }
    else
    {
        console.log("Got unknown message " + request);
    }
});

// From injected scripts
window.addEventListener('message', function(request) {
    if (! request.data.hasOwnProperty("type"))
    {
        return;
    }

    if (request.data.type == "addButton")
    {
        var elem = document.getElementById(request.data.id);
        do_add_single_button(elem, request.data.url);
    }
    else
    {
        console.log("Got unknown message " + request.toString());
    }
});<|endoftext|>


<commit_before>import Ember from 'ember';
import Component from 'ember-component';
import layout from '../templates/components/twitter-entities';

const { compare, getWithDefault } = Ember;
const { slice } = window.unicodeStringUtils;

export default Component.extend({
  layout: layout,

  didInitAttrs() {
    this._super(...arguments);
    let parts = [];
    parts = this._entitiesToArray(this.getAttr('entities'));
    parts = this._textToArray(this.getAttr('text'), parts);
    this.set('parts', parts);
  },

  _entitiesToArray(entities = {}) {
    let parts = [];

    Object.keys(entities).forEach((key) => {
      let typeEntities = getWithDefault(entities, key, []);

      typeEntities.forEach((entity) => {
        let component = this._componentForType(key);
        parts.push({ component, entity });
      });
    });

    parts.sort((a, b) => {
      return compare(a.entity.indices[0], b.entity.indices[0]);
    });

    return parts;
  },

  _textToArray(tweet = '', entityParts = []) {
    let text  = '';
    let parts = [];
    let index = 0;

    entityParts.forEach((part) => {
      let [start, end] = part.entity.indices;
      text = slice(tweet, index, start);

      if (text) { parts.push({ text }); }

      parts.push(part);
      index = end;
    });

    text = slice(tweet, index);
    if (text) { parts.push({ text }); }

    return parts;
  },

  _componentForType(type) {
    let types = {
      urls: 'url',
      hashtags: 'hashtag',
      user_mentions: 'user-mention',
      media: 'media'
    };

    let name = types[type];

    return this.getAttr(`${name}-component`) || `twitter-entity/${name}`;
  }
});
<commit_msg>phase out didInitAttrs<commit_after>import Ember from 'ember';
import Component from 'ember-component';
import layout from '../templates/components/twitter-entities';

const { keys } = Object;
const { compare, getWithDefault } = Ember;
const { slice } = window.unicodeStringUtils;

export default Component.extend({
  layout: layout,

  init() {
    this._super(...arguments);
    let parts = [];
    parts = this._entitiesToArray(this.getAttr('entities'));
    parts = this._textToArray(this.getAttr('text'), parts);
    this.set('parts', parts);
  },

  _entitiesToArray(entities = {}) {
    let parts = [];

    keys(entities).forEach((key) => {
      let typeEntities = getWithDefault(entities, key, []);

      typeEntities.forEach((entity) => {
        let component = this._componentForType(key);
        parts.push({ component, entity });
      });
    });

    parts.sort((a, b) => {
      return compare(a.entity.indices[0], b.entity.indices[0]);
    });

    return parts;
  },

  _textToArray(tweet = '', entityParts = []) {
    let text  = '';
    let parts = [];
    let index = 0;

    entityParts.forEach((part) => {
      let [start, end] = part.entity.indices;
      text = slice(tweet, index, start);

      if (text) { parts.push({ text }); }

      parts.push(part);
      index = end;
    });

    text = slice(tweet, index);
    if (text) { parts.push({ text }); }

    return parts;
  },

  _componentForType(type) {
    let types = {
      urls: 'url',
      hashtags: 'hashtag',
      user_mentions: 'user-mention',
      media: 'media'
    };

    let name = types[type];

    return this.getAttr(`${name}-component`) || `twitter-entity/${name}`;
  }
});
<|endoftext|>


<commit_before>/*! Singulr v0.1.0 | (c) Richard Liu | MIT License */
/*
    TODO:

     - compress code using yui compressor
     - or use closure (http://closure-compiler.appspot.com/home)
     - dynamically change favicon
*/

// (function () {
    var currentPage = '';
    var addedContent = [];
    var removalQueue = [];
    var options = {
        onPageLoaded: function() {}
    };

    var Constants = {
        // HOME_PAGE: 'home.html',
        BASE_PAGE: 'base.html',
        PAGE_ID: 'page',
        CONTENT_ID: 'content',
        SWIPE_TO_ID: 'swipe-to',
        SWIPE_FROM_ID: 'page'
    };

    window.Singulr = {
        init: function (userOptions) {
            // Add user options
            for (var option in userOptions) {
                options[option] = userOptions[option];
                Constants[option] = userOptions[option];
            }
            for (var option in userOptions) {
                options[option] = userOptions[option];
            }

            // load base on startup
            ajaxLoad(Constants.PAGE_ID, Constants.BASE_PAGE, function() {
                if (getPage() !== null && getPage() !== currentPage) {
                    loadPage(getPage());
                } else {
                    loadPage(Constants.HOME_PAGE);
                }
            });
        },
        getPage: getPage,
        loadPage: loadPage
    };

    function bindEventHandlers() {
        // unbind other event handlers
        var elements = document.getElementsByTagName('a');
        for (var i = 0; i < elements.length; i++) {
            elements[i].removeEventListener('click', onclick);
        }

        // bind event handlers
        elements = document.getElementsByTagName('a');
        for (var i = 0; i < elements.length; i++) {
            elements[i].addEventListener('click', onclick);
        }
        if (window.onhashchange === null) {
            window.onhashchange = onhashchange;
        }

        function onclick() {
            event.preventDefault();
            var page = this.getAttribute('href');

            if (currentPage !== page) {
                loadPage(page);
            }
        }

        function onhashchange() {
            var page = getPage();
            console.log('hash changed: ' + page);

            loadPage(page);
        }
    }

    function loadPage(page) {
        setPage(page);
        currentPage = page;

        ajaxLoad(Constants.CONTENT_ID, page, function(response) {
            // remove previous page's js and css
            if (addedContent !== []) {
                for (var i = 0; i < addedContent.length; i++) {
                    addNodeToRemovalQueue(addedContent[i]);
                }
                removeNodesInQueue();
                addedContent = [];
            }

            bindEventHandlers();

            // check if page is valid
            if (!response || typeof response !== 'string') {
                throw 'Invalid page';
            }

            var html = document.implementation.createHTMLDocument();
            // has enclosing html tags
            if (response.search('') > -1) {
                html.body.parentElement.innerHTML = response;
            } else {
                html.body.parentElement.innerHTML = '' + response + '';
            }

            var cssCode;
            var cssSrc;
            var jsCode = [];
            var jsSrc = [];
            var temp;

            /* set title */
            var titleElement = html.getElementsByTagName('title')[0];
            if (titleElement !== null && titleElement !== '') {
                document.title = titleElement.innerHTML;
            }

            /* load css */
            if (html.getElementsByTagName('style') !== []) {
                var styleElements = html.getElementsByTagName('style');

                for (var i = 0; i < styleElements.length; i++) {
                    cssCode = styleElements[i].innerHTML;
                    if (cssCode !== '') {
                        temp = document.createElement('style');
                        temp.innerHTML = cssCode;
                        document.getElementsByTagName('head')[0].appendChild(temp);
                        addedContent.push(temp);
                    }

                    addNodeToRemovalQueue(styleElements[i]);
                }
            }
            if (html.getElementsByTagName('link') !== []) {
                var linkElements = html.getElementsByTagName('link');

                for (var i = 0; i < linkElements.length; i++) {
                    cssSrc = linkElements[i].getAttribute('href');
                    temp = document.createElement('link');
                    temp.rel = 'stylesheet';
                    temp.type = 'text/css';
                    temp.href = cssSrc;
                    document.getElementsByTagName('head')[0].appendChild(temp);
                    addedContent.push(temp);

                    addNodeToRemovalQueue(linkElements[i]);
                }
            }

            /* load scripts */
            if (html.getElementsByTagName('script') !== []){
                var scriptElements = html.getElementsByTagName('script');

                for (var i = 0; i < scriptElements.length; i++) {
                    jsSrc = scriptElements[i].getAttribute('src');
                    jsCode = scriptElements[i].innerHTML;
                    if (jsSrc !== null) {
                        temp = document.createElement('script');
                        temp.src = jsSrc;
                        document.getElementsByTagName('body')[0].appendChild(temp);
                        addedContent.push(temp);
                    } else if (jsCode !== '') {
                        temp = document.createElement('script');
                        temp.innerHTML = jsCode;
                        document.getElementsByTagName('body')[0].appendChild(temp);
                        addedContent.push(temp);
                    }
                    addNodeToRemovalQueue(scriptElements[i]);
                }
            }

            removeNodesInQueue();

            return html.documentElement.getElementsByTagName('body')[0].innerHTML;
        });
    }

    // callback needs to return the html to be added (type string)
    function ajaxLoad(elementId, url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);

        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var res = callback(xhr.responseText);
                if (typeof res === 'string') {
                    document.getElementById(elementId).innerHTML = res;
                } else if (res === undefined) {
                    document.getElementById(elementId).innerHTML = xhr.responseText;
                } else {
                    throw new Error('Invalid callback return!');
                }
                options.onPageLoaded();
            }
        };

        xhr.send();
    }

    // removal queue needed because removing a node while iterating through a
    // list of nodes has bad effects
    function addNodeToRemovalQueue(node) {
        if (node === undefined || node === null) {
            throw new Error('No node provided');
        } else if (node.parentNode === null) {
            throw new Error('Node has been already removed');
        } else {
            removalQueue.push(node);
        }
    }

    function removeNodesInQueue() {
        for (var i = 0; i < removalQueue.length; i++) {
            var node = removalQueue[i];
            node.parentNode.removeChild(node);
        }
        removalQueue = [];
    }

    function setPage(page) {
        window.location.href = `${window.location.protocol}//${window.location.host + window.location.pathname}#!${page}`;
    }

    function getPage() {
        var hash = window.location.hash;
        var page;

        // #!page
        if (hash[1] === '!') {
            // #!page?option=value
            if (hash.search(/\?/) > 1) {
                page = hash.slice(2, hash.search(/\?/));
            // #!page
            } else {
                page = hash.slice(2);
            }
            return page;
        } else {
            return null;
        }
    }
// }());<commit_msg>bug fix<commit_after>/*! Singulr v0.1.0 | (c) Richard Liu | MIT License */
/*
    TODO:

     - compress code using yui compressor
     - or use closure (http://closure-compiler.appspot.com/home)
     - dynamically change favicon
*/

// (function () {
    var currentPage = '';
    var addedContent = [];
    var removalQueue = [];
    var options = {
        onPageLoaded: function() {}
    };

    var Constants = {
        HOME_PAGE: 'home.html',
        BASE_PAGE: 'base.html',
        PAGE_ID: 'page',
        CONTENT_ID: 'content',
        SWIPE_TO_ID: 'swipe-to',
        SWIPE_FROM_ID: 'page'
    };

    window.Singulr = {
        init: function (userOptions) {
            // Add user options
            for (var option in userOptions) {
                if (options[option] !== undefined) {
                    options[option] = userOptions[option];
                } else if (Constants[option] !== undefined) {
                    Constants[option] = userOptions[option];
                }
            }

            // load base on startup
            ajaxLoad(Constants.PAGE_ID, Constants.BASE_PAGE, function() {
                if (getPage() !== null && getPage() !== currentPage) {
                    loadPage(getPage());
                } else {
                    loadPage(Constants.HOME_PAGE);
                }
            });
        },
        getPage: getPage,
        loadPage: loadPage
    };

    function bindEventHandlers() {
        // unbind other event handlers
        var elements = document.getElementsByTagName('a');
        for (var i = 0; i < elements.length; i++) {
            elements[i].removeEventListener('click', onclick);
        }

        // bind event handlers
        elements = document.getElementsByTagName('a');
        for (var i = 0; i < elements.length; i++) {
            elements[i].addEventListener('click', onclick);
        }
        if (window.onhashchange === null) {
            window.onhashchange = onhashchange;
        }

        function onclick() {
            event.preventDefault();
            var page = this.getAttribute('href');

            if (currentPage !== page) {
                loadPage(page);
            }
        }

        function onhashchange() {
            var page = getPage();
            console.log('hash changed: ' + page);

            loadPage(page);
        }
    }

    function loadPage(page) {
        setPage(page);
        currentPage = page;

        ajaxLoad(Constants.CONTENT_ID, page, function(response) {
            // remove previous page's js and css
            if (addedContent !== []) {
                for (var i = 0; i < addedContent.length; i++) {
                    addNodeToRemovalQueue(addedContent[i]);
                }
                removeNodesInQueue();
                addedContent = [];
            }

            bindEventHandlers();

            // check if page is valid
            if (!response || typeof response !== 'string') {
                throw 'Invalid page';
            }

            var html = document.implementation.createHTMLDocument();
            // has enclosing html tags
            if (response.search('') > -1) {
                html.body.parentElement.innerHTML = response;
            } else {
                html.body.parentElement.innerHTML = '' + response + '';
            }

            var cssCode;
            var cssSrc;
            var jsCode = [];
            var jsSrc = [];
            var temp;

            /* set title */
            var titleElement = html.getElementsByTagName('title')[0];
            if (titleElement !== null && titleElement !== '') {
                document.title = titleElement.innerHTML;
            }

            /* load css */
            if (html.getElementsByTagName('style') !== []) {
                var styleElements = html.getElementsByTagName('style');

                for (var i = 0; i < styleElements.length; i++) {
                    cssCode = styleElements[i].innerHTML;
                    if (cssCode !== '') {
                        temp = document.createElement('style');
                        temp.innerHTML = cssCode;
                        document.getElementsByTagName('head')[0].appendChild(temp);
                        addedContent.push(temp);
                    }

                    addNodeToRemovalQueue(styleElements[i]);
                }
            }
            if (html.getElementsByTagName('link') !== []) {
                var linkElements = html.getElementsByTagName('link');

                for (var i = 0; i < linkElements.length; i++) {
                    cssSrc = linkElements[i].getAttribute('href');
                    temp = document.createElement('link');
                    temp.rel = 'stylesheet';
                    temp.type = 'text/css';
                    temp.href = cssSrc;
                    document.getElementsByTagName('head')[0].appendChild(temp);
                    addedContent.push(temp);

                    addNodeToRemovalQueue(linkElements[i]);
                }
            }

            /* load scripts */
            if (html.getElementsByTagName('script') !== []){
                var scriptElements = html.getElementsByTagName('script');

                for (var i = 0; i < scriptElements.length; i++) {
                    jsSrc = scriptElements[i].getAttribute('src');
                    jsCode = scriptElements[i].innerHTML;
                    if (jsSrc !== null) {
                        temp = document.createElement('script');
                        temp.src = jsSrc;
                        document.getElementsByTagName('body')[0].appendChild(temp);
                        addedContent.push(temp);
                    } else if (jsCode !== '') {
                        temp = document.createElement('script');
                        temp.innerHTML = jsCode;
                        document.getElementsByTagName('body')[0].appendChild(temp);
                        addedContent.push(temp);
                    }
                    addNodeToRemovalQueue(scriptElements[i]);
                }
            }

            removeNodesInQueue();

            return html.documentElement.getElementsByTagName('body')[0].innerHTML;
        });
    }

    // callback needs to return the html to be added (type string)
    function ajaxLoad(elementId, url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);

        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var res = callback(xhr.responseText);
                if (typeof res === 'string') {
                    document.getElementById(elementId).innerHTML = res;
                } else if (res === undefined) {
                    document.getElementById(elementId).innerHTML = xhr.responseText;
                } else {
                    throw new Error('Invalid callback return!');
                }
                options.onPageLoaded();
            }
        };

        xhr.send();
    }

    // removal queue needed because removing a node while iterating through a
    // list of nodes has bad effects
    function addNodeToRemovalQueue(node) {
        if (node === undefined || node === null) {
            throw new Error('No node provided');
        } else if (node.parentNode === null) {
            throw new Error('Node has been already removed');
        } else {
            removalQueue.push(node);
        }
    }

    function removeNodesInQueue() {
        for (var i = 0; i < removalQueue.length; i++) {
            var node = removalQueue[i];
            node.parentNode.removeChild(node);
        }
        removalQueue = [];
    }

    function setPage(page) {
        window.location.href = `${window.location.protocol}//${window.location.host + window.location.pathname}#!${page}`;
    }

    function getPage() {
        var hash = window.location.hash;
        var page;

        // #!page
        if (hash[1] === '!') {
            // #!page?option=value
            if (hash.search(/\?/) > 1) {
                page = hash.slice(2, hash.search(/\?/));
            // #!page
            } else {
                page = hash.slice(2);
            }
            return page;
        } else {
            return null;
        }
    }
// }());<|endoftext|>