Skip to content

Instantly share code, notes, and snippets.

@kimardenmiller
Created August 29, 2014 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kimardenmiller/cfa0981b7f60d4c3c502 to your computer and use it in GitHub Desktop.
Save kimardenmiller/cfa0981b7f60d4c3c502 to your computer and use it in GitHub Desktop.
Rails JavaScript Assets passed to Karma
/* ========================================================================
* Bootstrap: transition.js v3.1.1
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false, $el = this
$(this).one($.support.transition.end, function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
$(function () {
$.support.transition = transitionEnd()
})
}(jQuery);
/* ========================================================================
* Bootstrap: collapse.js v3.1.1
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.transitioning = null
if (this.options.parent) this.$parent = $(this.options.parent)
if (this.options.toggle) this.toggle()
}
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var actives = this.$parent && this.$parent.find('> .panel > .in')
if (actives && actives.length) {
var hasData = actives.data('bs.collapse')
if (hasData && hasData.transitioning) return
actives.collapse('hide')
hasData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
this.transitioning = 1
var complete = function (e) {
if (e && e.target != this.$element[0]) {
this.$element
.one($.support.transition.end, $.proxy(complete, this))
return
}
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('auto')
this.transitioning = 0
this.$element.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one($.support.transition.end, $.proxy(complete, this))
.emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse')
.removeClass('in')
this.transitioning = 1
var complete = function (e) {
if (e && e.target != this.$element[0]) {
this.$element
.one($.support.transition.end, $.proxy(complete, this))
return
}
this.transitioning = 0
this.$element
.trigger('hidden.bs.collapse')
.removeClass('collapsing')
.addClass('collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one($.support.transition.end, $.proxy(complete, this))
.emulateTransitionEnd(350)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
var old = $.fn.collapse
$.fn.collapse = function (option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && option == 'show') option = !option
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this), href
var target = $this.attr('data-target')
|| e.preventDefault()
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
var $target = $(target)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
var parent = $this.attr('data-parent')
var $parent = parent && $(parent)
if (!data || !data.transitioning) {
if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed')
$this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
}
$target.collapse(option)
})
}(jQuery);
/**
* AngularUI - The companion suite for AngularJS
* @version v0.4.0 - 2013-02-15
* @link http://angular-ui.github.com
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
angular.module('ui.config', []).value('ui.config', {});
angular.module('ui.filters', ['ui.config']);
angular.module('ui.directives', ['ui.config']);
angular.module('ui', ['ui.filters', 'ui.directives', 'ui.config']);
/**
* Animates the injection of new DOM elements by simply creating the DOM with a class and then immediately removing it
* Animations must be done using CSS3 transitions, but provide excellent flexibility
*
* @todo Add proper support for animating out
* @param [options] {mixed} Can be an object with multiple options, or a string with the animation class
* class {string} the CSS class(es) to use. For example, 'ui-hide' might be an excellent alternative class.
* @example <li ng-repeat="item in items" ui-animate=" 'ui-hide' ">{{item}}</li>
*/
angular.module('ui.directives').directive('uiAnimate', ['ui.config', '$timeout', function (uiConfig, $timeout) {
var options = {};
if (angular.isString(uiConfig.animate)) {
options['class'] = uiConfig.animate;
} else if (uiConfig.animate) {
options = uiConfig.animate;
}
return {
restrict: 'A', // supports using directive as element, attribute and class
link: function ($scope, element, attrs) {
var opts = {};
if (attrs.uiAnimate) {
opts = $scope.$eval(attrs.uiAnimate);
if (angular.isString(opts)) {
opts = {'class': opts};
}
}
opts = angular.extend({'class': 'ui-animate'}, options, opts);
element.addClass(opts['class']);
$timeout(function () {
element.removeClass(opts['class']);
}, 20, false);
}
};
}]);
/*
* AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
* API @ http://arshaw.com/fullcalendar/
*
* Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches (eventSources.length + eventSources[i].length) for changes.
* Can also take in multiple event urls as a source object(s) and feed the events per view.
* The calendar will watch any eventSource array and update itself when a delta is created
* An equalsTracker attrs has been added for use cases that would render the overall length tracker the same even though the events have changed to force updates.
*
*/
angular.module('ui.directives').directive('uiCalendar',['ui.config', '$parse', function (uiConfig,$parse) {
uiConfig.uiCalendar = uiConfig.uiCalendar || {};
//returns calendar
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, $timeout) {
var sources = scope.$eval(attrs.ngModel);
var tracker = 0;
/* returns the length of all source arrays plus the length of eventSource itself */
var getSources = function () {
var equalsTracker = scope.$eval(attrs.equalsTracker);
tracker = 0;
angular.forEach(sources,function(value,key){
if(angular.isArray(value)){
tracker += value.length;
}
});
if(angular.isNumber(equalsTracker)){
return tracker + sources.length + equalsTracker;
}else{
return tracker + sources.length;
}
};
/* update the calendar with the correct options */
function update() {
//calendar object exposed on scope
scope.calendar = elm.html('');
var view = scope.calendar.fullCalendar('getView');
if(view){
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
}
/* If the calendar has options added then render them */
var expression,
options = {
defaultView : view,
eventSources: sources
};
if (attrs.uiCalendar) {
expression = scope.$eval(attrs.uiCalendar);
} else {
expression = {};
}
angular.extend(options, uiConfig.uiCalendar, expression);
scope.calendar.fullCalendar(options);
}
update();
/* watches all eventSources */
scope.$watch(getSources, function( newVal, oldVal )
{
update();
});
}
};
}]);
/*global angular, CodeMirror, Error*/
/**
* Binds a CodeMirror widget to a <textarea> element.
*/
angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$timeout', function (uiConfig, $timeout) {
'use strict';
var events = ["cursorActivity", "viewportChange", "gutterClick", "focus", "blur", "scroll", "update"];
return {
restrict:'A',
require:'ngModel',
link:function (scope, elm, attrs, ngModel) {
var options, opts, onChange, deferCodeMirror, codeMirror;
if (elm[0].type !== 'textarea') {
throw new Error('uiCodemirror3 can only be applied to a textarea element');
}
options = uiConfig.codemirror || {};
opts = angular.extend({}, options, scope.$eval(attrs.uiCodemirror));
onChange = function (aEvent) {
return function (instance, changeObj) {
var newValue = instance.getValue();
if (newValue !== ngModel.$viewValue) {
ngModel.$setViewValue(newValue);
scope.$apply();
}
if (typeof aEvent === "function")
aEvent(instance, changeObj);
};
};
deferCodeMirror = function () {
codeMirror = CodeMirror.fromTextArea(elm[0], opts);
codeMirror.on("change", onChange(opts.onChange));
for (var i = 0, n = events.length, aEvent; i < n; ++i) {
aEvent = opts["on" + events[i].charAt(0).toUpperCase() + events[i].slice(1)];
if (aEvent === void 0) continue;
if (typeof aEvent !== "function") continue;
codeMirror.on(events[i], aEvent);
}
// CodeMirror expects a string, so make sure it gets one.
// This does not change the model.
ngModel.$formatters.push(function (value) {
if (angular.isUndefined(value) || value === null) {
return '';
}
else if (angular.isObject(value) || angular.isArray(value)) {
throw new Error('ui-codemirror cannot use an object or an array as a model');
}
return value;
});
// Override the ngModelController $render method, which is what gets called when the model is updated.
// This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
ngModel.$render = function () {
codeMirror.setValue(ngModel.$viewValue);
};
// Watch ui-refresh and refresh the directive
if (attrs.uiRefresh) {
scope.$watch(attrs.uiRefresh, function(newVal, oldVal){
// Skip the initial watch firing
if (newVal !== oldVal)
$timeout(codeMirror.refresh);
});
}
};
$timeout(deferCodeMirror);
}
};
}]);
/*
Gives the ability to style currency based on its sign.
*/
angular.module('ui.directives').directive('uiCurrency', ['ui.config', 'currencyFilter' , function (uiConfig, currencyFilter) {
var options = {
pos: 'ui-currency-pos',
neg: 'ui-currency-neg',
zero: 'ui-currency-zero'
};
if (uiConfig.currency) {
angular.extend(options, uiConfig.currency);
}
return {
restrict: 'EAC',
require: 'ngModel',
link: function (scope, element, attrs, controller) {
var opts, // instance-specific options
renderview,
value;
opts = angular.extend({}, options, scope.$eval(attrs.uiCurrency));
renderview = function (viewvalue) {
var num;
num = viewvalue * 1;
element.toggleClass(opts.pos, (num > 0) );
element.toggleClass(opts.neg, (num < 0) );
element.toggleClass(opts.zero, (num === 0) );
if (viewvalue === '') {
element.text('');
} else {
element.text(currencyFilter(num, opts.symbol));
}
return true;
};
controller.$render = function () {
value = controller.$viewValue;
element.val(value);
renderview(value);
};
}
};
}]);
/*global angular */
/*
jQuery UI Datepicker plugin wrapper
@note If ≤ IE8 make sure you have a polyfill for Date.toISOString()
@param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto ui.config
*/
angular.module('ui.directives')
.directive('uiDate', ['ui.config', function (uiConfig) {
'use strict';
var options;
options = {};
if (angular.isObject(uiConfig.date)) {
angular.extend(options, uiConfig.date);
}
return {
require:'?ngModel',
link:function (scope, element, attrs, controller) {
var getOptions = function () {
return angular.extend({}, uiConfig.date, scope.$eval(attrs.uiDate));
};
var initDateWidget = function () {
var opts = getOptions();
// If we have a controller (i.e. ngModelController) then wire it up
if (controller) {
var updateModel = function () {
scope.$apply(function () {
var date = element.datepicker("getDate");
element.datepicker("setDate", element.val());
controller.$setViewValue(date);
element.blur();
});
};
if (opts.onSelect) {
// Caller has specified onSelect, so call this as well as updating the model
var userHandler = opts.onSelect;
opts.onSelect = function (value, picker) {
updateModel();
scope.$apply(function() {
userHandler(value, picker);
});
};
} else {
// No onSelect already specified so just update the model
opts.onSelect = updateModel;
}
// In case the user changes the text directly in the input box
element.bind('change', updateModel);
// Update the date picker when the model changes
controller.$render = function () {
var date = controller.$viewValue;
if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
}
element.datepicker("setDate", date);
};
}
// If we don't destroy the old one it doesn't update properly when the config changes
element.datepicker('destroy');
// Create the new datepicker widget
element.datepicker(opts);
if ( controller ) {
// Force a render to override whatever is in the input text box
controller.$render();
}
};
// Watch for changes to the directives options
scope.$watch(getOptions, initDateWidget, true);
}
};
}
])
.directive('uiDateFormat', ['ui.config', function(uiConfig) {
var directive = {
require:'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var dateFormat = attrs.uiDateFormat || uiConfig.dateFormat;
if ( dateFormat ) {
// Use the datepicker with the attribute value as the dateFormat string to convert to and from a string
modelCtrl.$formatters.push(function(value) {
if (angular.isString(value) ) {
return $.datepicker.parseDate(dateFormat, value);
}
});
modelCtrl.$parsers.push(function(value){
if (value) {
return $.datepicker.formatDate(dateFormat, value);
}
});
} else {
// Default to ISO formatting
modelCtrl.$formatters.push(function(value) {
if (angular.isString(value) ) {
return new Date(value);
}
});
modelCtrl.$parsers.push(function(value){
if (value) {
return value.toISOString();
}
});
}
}
};
return directive;
}]);
/**
* General-purpose Event binding. Bind any event not natively supported by Angular
* Pass an object with keynames for events to ui-event
* Allows $event object and $params object to be passed
*
* @example <input ui-event="{ focus : 'counter++', blur : 'someCallback()' }">
* @example <input ui-event="{ myCustomEvent : 'myEventHandler($event, $params)'}">
*
* @param ui-event {string|object literal} The event to bind to as a string or a hash of events with their callbacks
*/
angular.module('ui.directives').directive('uiEvent', ['$parse',
function ($parse) {
return function (scope, elm, attrs) {
var events = scope.$eval(attrs.uiEvent);
angular.forEach(events, function (uiEvent, eventName) {
var fn = $parse(uiEvent);
elm.bind(eventName, function (evt) {
var params = Array.prototype.slice.call(arguments);
//Take out first paramater (event object);
params = params.splice(1);
scope.$apply(function () {
fn(scope, {$event: evt, $params: params});
});
});
});
};
}]);
/*
* Defines the ui-if tag. This removes/adds an element from the dom depending on a condition
* Originally created by @tigbro, for the @jquery-mobile-angular-adapter
* https://github.com/tigbro/jquery-mobile-angular-adapter
*/
angular.module('ui.directives').directive('uiIf', [function () {
return {
transclude: 'element',
priority: 1000,
terminal: true,
restrict: 'A',
compile: function (element, attr, transclude) {
return function (scope, element, attr) {
var childElement;
var childScope;
scope.$watch(attr['uiIf'], function (newValue) {
if (childElement) {
childElement.remove();
childElement = undefined;
}
if (childScope) {
childScope.$destroy();
childScope = undefined;
}
if (newValue) {
childScope = scope.$new();
transclude(childScope, function (clone) {
childElement = clone;
element.after(clone);
});
}
});
};
}
};
}]);
/**
* General-purpose jQuery wrapper. Simply pass the plugin name as the expression.
*
* It is possible to specify a default set of parameters for each jQuery plugin.
* Under the jq key, namespace each plugin by that which will be passed to ui-jq.
* Unfortunately, at this time you can only pre-define the first parameter.
* @example { jq : { datepicker : { showOn:'click' } } }
*
* @param ui-jq {string} The $elm.[pluginName]() to call.
* @param [ui-options] {mixed} Expression to be evaluated and passed as options to the function
* Multiple parameters can be separated by commas
* @param [ui-refresh] {expression} Watch expression and refire plugin on changes
*
* @example <input ui-jq="datepicker" ui-options="{showOn:'click'},secondParameter,thirdParameter" ui-refresh="iChange">
*/
angular.module('ui.directives').directive('uiJq', ['ui.config', '$timeout', function uiJqInjectingFunction(uiConfig, $timeout) {
return {
restrict: 'A',
compile: function uiJqCompilingFunction(tElm, tAttrs) {
if (!angular.isFunction(tElm[tAttrs.uiJq])) {
throw new Error('ui-jq: The "' + tAttrs.uiJq + '" function does not exist');
}
var options = uiConfig.jq && uiConfig.jq[tAttrs.uiJq];
return function uiJqLinkingFunction(scope, elm, attrs) {
var linkOptions = [];
// If ui-options are passed, merge (or override) them onto global defaults and pass to the jQuery method
if (attrs.uiOptions) {
linkOptions = scope.$eval('[' + attrs.uiOptions + ']');
if (angular.isObject(options) && angular.isObject(linkOptions[0])) {
linkOptions[0] = angular.extend({}, options, linkOptions[0]);
}
} else if (options) {
linkOptions = [options];
}
// If change compatibility is enabled, the form input's "change" event will trigger an "input" event
if (attrs.ngModel && elm.is('select,input,textarea')) {
elm.on('change', function() {
elm.trigger('input');
});
}
// Call jQuery method and pass relevant options
function callPlugin() {
$timeout(function() {
elm[attrs.uiJq].apply(elm, linkOptions);
}, 0, false);
}
// If ui-refresh is used, re-fire the the method upon every change
if (attrs.uiRefresh) {
scope.$watch(attrs.uiRefresh, function(newVal) {
callPlugin();
});
}
callPlugin();
};
}
};
}]);
angular.module('ui.directives').factory('keypressHelper', ['$parse', function keypress($parse){
var keysByCode = {
8: 'backspace',
9: 'tab',
13: 'enter',
27: 'esc',
32: 'space',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'insert',
46: 'delete'
};
var capitaliseFirstLetter = function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
return function(mode, scope, elm, attrs) {
var params, combinations = [];
params = scope.$eval(attrs['ui'+capitaliseFirstLetter(mode)]);
// Prepare combinations for simple checking
angular.forEach(params, function (v, k) {
var combination, expression;
expression = $parse(v);
angular.forEach(k.split(' '), function(variation) {
combination = {
expression: expression,
keys: {}
};
angular.forEach(variation.split('-'), function (value) {
combination.keys[value] = true;
});
combinations.push(combination);
});
});
// Check only matching of pressed keys one of the conditions
elm.bind(mode, function (event) {
// No need to do that inside the cycle
var altPressed = event.metaKey || event.altKey;
var ctrlPressed = event.ctrlKey;
var shiftPressed = event.shiftKey;
var keyCode = event.keyCode;
// normalize keycodes
if (mode === 'keypress' && !shiftPressed && keyCode >= 97 && keyCode <= 122) {
keyCode = keyCode - 32;
}
// Iterate over prepared combinations
angular.forEach(combinations, function (combination) {
var mainKeyPressed = (combination.keys[keysByCode[event.keyCode]] || combination.keys[event.keyCode.toString()]) || false;
var altRequired = combination.keys.alt || false;
var ctrlRequired = combination.keys.ctrl || false;
var shiftRequired = combination.keys.shift || false;
if (
mainKeyPressed &&
( altRequired == altPressed ) &&
( ctrlRequired == ctrlPressed ) &&
( shiftRequired == shiftPressed )
) {
// Run the function
scope.$apply(function () {
combination.expression(scope, { '$event': event });
});
}
});
});
};
}]);
/**
* Bind one or more handlers to particular keys or their combination
* @param hash {mixed} keyBindings Can be an object or string where keybinding expression of keys or keys combinations and AngularJS Exspressions are set. Object syntax: "{ keys1: expression1 [, keys2: expression2 [ , ... ]]}". String syntax: ""expression1 on keys1 [ and expression2 on keys2 [ and ... ]]"". Expression is an AngularJS Expression, and key(s) are dash-separated combinations of keys and modifiers (one or many, if any. Order does not matter). Supported modifiers are 'ctrl', 'shift', 'alt' and key can be used either via its keyCode (13 for Return) or name. Named keys are 'backspace', 'tab', 'enter', 'esc', 'space', 'pageup', 'pagedown', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'.
* @example <input ui-keypress="{enter:'x = 1', 'ctrl-shift-space':'foo()', 'shift-13':'bar()'}" /> <input ui-keypress="foo = 2 on ctrl-13 and bar('hello') on shift-esc" />
**/
angular.module('ui.directives').directive('uiKeydown', ['keypressHelper', function(keypressHelper){
return {
link: function (scope, elm, attrs) {
keypressHelper('keydown', scope, elm, attrs);
}
};
}]);
angular.module('ui.directives').directive('uiKeypress', ['keypressHelper', function(keypressHelper){
return {
link: function (scope, elm, attrs) {
keypressHelper('keypress', scope, elm, attrs);
}
};
}]);
angular.module('ui.directives').directive('uiKeyup', ['keypressHelper', function(keypressHelper){
return {
link: function (scope, elm, attrs) {
keypressHelper('keyup', scope, elm, attrs);
}
};
}]);
(function () {
var app = angular.module('ui.directives');
//Setup map events from a google map object to trigger on a given element too,
//then we just use ui-event to catch events from an element
function bindMapEvents(scope, eventsStr, googleObject, element) {
angular.forEach(eventsStr.split(' '), function (eventName) {
//Prefix all googlemap events with 'map-', so eg 'click'
//for the googlemap doesn't interfere with a normal 'click' event
var $event = { type: 'map-' + eventName };
google.maps.event.addListener(googleObject, eventName, function (evt) {
element.triggerHandler(angular.extend({}, $event, evt));
//We create an $apply if it isn't happening. we need better support for this
//We don't want to use timeout because tons of these events fire at once,
//and we only need one $apply
if (!scope.$$phase) scope.$apply();
});
});
}
app.directive('uiMap',
['ui.config', '$parse', function (uiConfig, $parse) {
var mapEvents = 'bounds_changed center_changed click dblclick drag dragend ' +
'dragstart heading_changed idle maptypeid_changed mousemove mouseout ' +
'mouseover projection_changed resize rightclick tilesloaded tilt_changed ' +
'zoom_changed';
var options = uiConfig.map || {};
return {
restrict: 'A',
//doesn't work as E for unknown reason
link: function (scope, elm, attrs) {
var opts = angular.extend({}, options, scope.$eval(attrs.uiOptions));
var map = new google.maps.Map(elm[0], opts);
var model = $parse(attrs.uiMap);
//Set scope variable for the map
model.assign(scope, map);
bindMapEvents(scope, mapEvents, map, elm);
}
};
}]);
app.directive('uiMapInfoWindow',
['ui.config', '$parse', '$compile', function (uiConfig, $parse, $compile) {
var infoWindowEvents = 'closeclick content_change domready ' +
'position_changed zindex_changed';
var options = uiConfig.mapInfoWindow || {};
return {
link: function (scope, elm, attrs) {
var opts = angular.extend({}, options, scope.$eval(attrs.uiOptions));
opts.content = elm[0];
var model = $parse(attrs.uiMapInfoWindow);
var infoWindow = model(scope);
if (!infoWindow) {
infoWindow = new google.maps.InfoWindow(opts);
model.assign(scope, infoWindow);
}
bindMapEvents(scope, infoWindowEvents, infoWindow, elm);
/* The info window's contents dont' need to be on the dom anymore,
google maps has them stored. So we just replace the infowindow element
with an empty div. (we don't just straight remove it from the dom because
straight removing things from the dom can mess up angular) */
elm.replaceWith('<div></div>');
//Decorate infoWindow.open to $compile contents before opening
var _open = infoWindow.open;
infoWindow.open = function open(a1, a2, a3, a4, a5, a6) {
$compile(elm.contents())(scope);
_open.call(infoWindow, a1, a2, a3, a4, a5, a6);
};
}
};
}]);
/*
* Map overlay directives all work the same. Take map marker for example
* <ui-map-marker="myMarker"> will $watch 'myMarker' and each time it changes,
* it will hook up myMarker's events to the directive dom element. Then
* ui-event will be able to catch all of myMarker's events. Super simple.
*/
function mapOverlayDirective(directiveName, events) {
app.directive(directiveName, [function () {
return {
restrict: 'A',
link: function (scope, elm, attrs) {
scope.$watch(attrs[directiveName], function (newObject) {
bindMapEvents(scope, events, newObject, elm);
});
}
};
}]);
}
mapOverlayDirective('uiMapMarker',
'animation_changed click clickable_changed cursor_changed ' +
'dblclick drag dragend draggable_changed dragstart flat_changed icon_changed ' +
'mousedown mouseout mouseover mouseup position_changed rightclick ' +
'shadow_changed shape_changed title_changed visible_changed zindex_changed');
mapOverlayDirective('uiMapPolyline',
'click dblclick mousedown mousemove mouseout mouseover mouseup rightclick');
mapOverlayDirective('uiMapPolygon',
'click dblclick mousedown mousemove mouseout mouseover mouseup rightclick');
mapOverlayDirective('uiMapRectangle',
'bounds_changed click dblclick mousedown mousemove mouseout mouseover ' +
'mouseup rightclick');
mapOverlayDirective('uiMapCircle',
'center_changed click dblclick mousedown mousemove ' +
'mouseout mouseover mouseup radius_changed rightclick');
mapOverlayDirective('uiMapGroundOverlay',
'click dblclick');
})();
/*
Attaches jquery-ui input mask onto input element
*/
angular.module('ui.directives').directive('uiMask', [
function () {
return {
require:'ngModel',
link:function ($scope, element, attrs, controller) {
/* We override the render method to run the jQuery mask plugin
*/
controller.$render = function () {
var value = controller.$viewValue || '';
element.val(value);
element.mask($scope.$eval(attrs.uiMask));
};
/* Add a parser that extracts the masked value into the model but only if the mask is valid
*/
controller.$parsers.push(function (value) {
//the second check (or) is only needed due to the fact that element.isMaskValid() will keep returning undefined
//until there was at least one key event
var isValid = element.isMaskValid() || angular.isUndefined(element.isMaskValid()) && element.val().length>0;
controller.$setValidity('mask', isValid);
return isValid ? value : undefined;
});
/* When keyup, update the view value
*/
element.bind('keyup', function () {
$scope.$apply(function () {
controller.$setViewValue(element.mask());
});
});
}
};
}
]);
/**
* Add a clear button to form inputs to reset their value
*/
angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiConfig) {
var resetValue = null;
if (uiConfig.reset !== undefined)
resetValue = uiConfig.reset;
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var aElement;
aElement = angular.element('<a class="ui-reset" />');
elm.wrap('<span class="ui-resetwrap" />').after(aElement);
aElement.bind('click', function (e) {
e.preventDefault();
scope.$apply(function () {
if (attrs.uiReset)
ctrl.$setViewValue(scope.$eval(attrs.uiReset));
else
ctrl.$setViewValue(resetValue);
ctrl.$render();
});
});
}
};
}]);
/**
* Set a $uiRoute boolean to see if the current route matches
*/
angular.module('ui.directives').directive('uiRoute', ['$location', '$parse', function ($location, $parse) {
return {
restrict: 'AC',
compile: function(tElement, tAttrs) {
var useProperty;
if (tAttrs.uiRoute) {
useProperty = 'uiRoute';
} else if (tAttrs.ngHref) {
useProperty = 'ngHref';
} else if (tAttrs.href) {
useProperty = 'href';
} else {
throw new Error('uiRoute missing a route or href property on ' + tElement[0]);
}
return function ($scope, elm, attrs) {
var modelSetter = $parse(attrs.ngModel || attrs.routeModel || '$uiRoute').assign;
var watcher = angular.noop;
// Used by href and ngHref
function staticWatcher(newVal) {
if ((hash = newVal.indexOf('#')) > -1)
newVal = newVal.substr(hash + 1);
watcher = function watchHref() {
modelSetter($scope, ($location.path().indexOf(newVal) > -1));
};
watcher();
}
// Used by uiRoute
function regexWatcher(newVal) {
if ((hash = newVal.indexOf('#')) > -1)
newVal = newVal.substr(hash + 1);
watcher = function watchRegex() {
var regexp = new RegExp('^' + newVal + '$', ['i']);
modelSetter($scope, regexp.test($location.path()));
};
watcher();
}
switch (useProperty) {
case 'uiRoute':
// if uiRoute={{}} this will be undefined, otherwise it will have a value and $observe() never gets triggered
if (attrs.uiRoute)
regexWatcher(attrs.uiRoute);
else
attrs.$observe('uiRoute', regexWatcher);
break;
case 'ngHref':
// Setup watcher() every time ngHref changes
if (attrs.ngHref)
staticWatcher(attrs.ngHref);
else
attrs.$observe('ngHref', staticWatcher);
break;
case 'href':
// Setup watcher()
staticWatcher(attrs.href);
}
$scope.$on('$routeChangeSuccess', function(){
watcher();
});
}
}
};
}]);
/*global angular, $, document*/
/**
* Adds a 'ui-scrollfix' class to the element when the page scrolls past it's position.
* @param [offset] {int} optional Y-offset to override the detected offset.
* Takes 300 (absolute) or -300 or +300 (relative to detected)
*/
angular.module('ui.directives').directive('uiScrollfix', ['$window', function ($window) {
'use strict';
return {
link: function (scope, elm, attrs) {
var top = elm.offset().top;
if (!attrs.uiScrollfix) {
attrs.uiScrollfix = top;
} else {
// chartAt is generally faster than indexOf: http://jsperf.com/indexof-vs-chartat
if (attrs.uiScrollfix.charAt(0) === '-') {
attrs.uiScrollfix = top - attrs.uiScrollfix.substr(1);
} else if (attrs.uiScrollfix.charAt(0) === '+') {
attrs.uiScrollfix = top + parseFloat(attrs.uiScrollfix.substr(1));
}
}
angular.element($window).on('scroll.ui-scrollfix', function () {
// if pageYOffset is defined use it, otherwise use other crap for IE
var offset;
if (angular.isDefined($window.pageYOffset)) {
offset = $window.pageYOffset;
} else {
var iebody = (document.compatMode && document.compatMode !== "BackCompat") ? document.documentElement : document.body;
offset = iebody.scrollTop;
}
if (!elm.hasClass('ui-scrollfix') && offset > attrs.uiScrollfix) {
elm.addClass('ui-scrollfix');
} else if (elm.hasClass('ui-scrollfix') && offset < attrs.uiScrollfix) {
elm.removeClass('ui-scrollfix');
}
});
}
};
}]);
/**
* Enhanced Select2 Dropmenus
*
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
*/
angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', function (uiConfig, $timeout) {
var options = {};
if (uiConfig.select2) {
angular.extend(options, uiConfig.select2);
}
return {
require: '?ngModel',
compile: function (tElm, tAttrs) {
var watch,
repeatOption,
repeatAttr,
isSelect = tElm.is('select'),
isMultiple = (tAttrs.multiple !== undefined);
// Enable watching of the options dataset if in use
if (tElm.is('select')) {
repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');
if (repeatOption.length) {
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
}
}
return function (scope, elm, attrs, controller) {
// instance-specific options
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
if (isSelect) {
// Use <select multiple> instead
delete opts.multiple;
delete opts.initSelection;
} else if (isMultiple) {
opts.multiple = true;
}
if (controller) {
// Watch the model for programmatic changes
controller.$render = function () {
if (isSelect) {
elm.select2('val', controller.$modelValue);
} else {
if (isMultiple) {
if (!controller.$modelValue) {
elm.select2('data', []);
} else if (angular.isArray(controller.$modelValue)) {
elm.select2('data', controller.$modelValue);
} else {
elm.select2('val', controller.$modelValue);
}
} else {
if (angular.isObject(controller.$modelValue)) {
elm.select2('data', controller.$modelValue);
} else {
elm.select2('val', controller.$modelValue);
}
}
}
};
// Watch the options dataset for changes
if (watch) {
scope.$watch(watch, function (newVal, oldVal, scope) {
if (!newVal) return;
// Delayed so that the options have time to be rendered
$timeout(function () {
elm.select2('val', controller.$viewValue);
// Refresh angular to remove the superfluous option
elm.trigger('change');
});
});
}
if (!isSelect) {
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
elm.bind("change", function () {
scope.$apply(function () {
controller.$setViewValue(elm.select2('data'));
});
});
if (opts.initSelection) {
var initSelection = opts.initSelection;
opts.initSelection = function (element, callback) {
initSelection(element, function (value) {
controller.$setViewValue(value);
callback(value);
});
};
}
}
}
attrs.$observe('disabled', function (value) {
elm.select2(value && 'disable' || 'enable');
});
if (attrs.ngMultiple) {
scope.$watch(attrs.ngMultiple, function(newVal) {
elm.select2(opts);
});
}
// Set initial value since Angular doesn't
elm.val(scope.$eval(attrs.ngModel));
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
$timeout(function () {
elm.select2(opts);
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
if (!opts.initSelection && !isSelect)
controller.$setViewValue(elm.select2('data'));
});
};
}
};
}]);
/**
* uiShow Directive
*
* Adds a 'ui-show' class to the element instead of display:block
* Created to allow tighter control of CSS without bulkier directives
*
* @param expression {boolean} evaluated expression to determine if the class should be added
*/
angular.module('ui.directives').directive('uiShow', [function () {
return function (scope, elm, attrs) {
scope.$watch(attrs.uiShow, function (newVal, oldVal) {
if (newVal) {
elm.addClass('ui-show');
} else {
elm.removeClass('ui-show');
}
});
};
}])
/**
* uiHide Directive
*
* Adds a 'ui-hide' class to the element instead of display:block
* Created to allow tighter control of CSS without bulkier directives
*
* @param expression {boolean} evaluated expression to determine if the class should be added
*/
.directive('uiHide', [function () {
return function (scope, elm, attrs) {
scope.$watch(attrs.uiHide, function (newVal, oldVal) {
if (newVal) {
elm.addClass('ui-hide');
} else {
elm.removeClass('ui-hide');
}
});
};
}])
/**
* uiToggle Directive
*
* Adds a class 'ui-show' if true, and a 'ui-hide' if false to the element instead of display:block/display:none
* Created to allow tighter control of CSS without bulkier directives. This also allows you to override the
* default visibility of the element using either class.
*
* @param expression {boolean} evaluated expression to determine if the class should be added
*/
.directive('uiToggle', [function () {
return function (scope, elm, attrs) {
scope.$watch(attrs.uiToggle, function (newVal, oldVal) {
if (newVal) {
elm.removeClass('ui-hide').addClass('ui-show');
} else {
elm.removeClass('ui-show').addClass('ui-hide');
}
});
};
}]);
/*
jQuery UI Sortable plugin wrapper
@param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
*/
angular.module('ui.directives').directive('uiSortable', [
'ui.config', function(uiConfig) {
return {
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
var onReceive, onRemove, onStart, onUpdate, opts, _receive, _remove, _start, _update;
opts = angular.extend({}, uiConfig.sortable, scope.$eval(attrs.uiSortable));
if (ngModel) {
ngModel.$render = function() {
element.sortable( "refresh" );
};
onStart = function(e, ui) {
// Save position of dragged item
ui.item.sortable = { index: ui.item.index() };
};
onUpdate = function(e, ui) {
// For some reason the reference to ngModel in stop() is wrong
ui.item.sortable.resort = ngModel;
};
onReceive = function(e, ui) {
ui.item.sortable.relocate = true;
// added item to array into correct position and set up flag
ngModel.$modelValue.splice(ui.item.index(), 0, ui.item.sortable.moved);
};
onRemove = function(e, ui) {
// copy data into item
if (ngModel.$modelValue.length === 1) {
ui.item.sortable.moved = ngModel.$modelValue.splice(0, 1)[0];
} else {
ui.item.sortable.moved = ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0];
}
};
onStop = function(e, ui) {
// digest all prepared changes
if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
// Fetch saved and current position of dropped element
var end, start;
start = ui.item.sortable.index;
end = ui.item.index();
if (start < end)
end--;
// Reorder array and apply change to scope
ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
}
if (ui.item.sortable.resort || ui.item.sortable.relocate) {
scope.$apply();
}
};
// If user provided 'start' callback compose it with onStart function
_start = opts.start;
opts.start = function(e, ui) {
onStart(e, ui);
if (typeof _start === "function")
_start(e, ui);
};
// If user provided 'start' callback compose it with onStart function
_stop = opts.stop;
opts.stop = function(e, ui) {
onStop(e, ui);
if (typeof _stop === "function")
_stop(e, ui);
};
// If user provided 'update' callback compose it with onUpdate function
_update = opts.update;
opts.update = function(e, ui) {
onUpdate(e, ui);
if (typeof _update === "function")
_update(e, ui);
};
// If user provided 'receive' callback compose it with onReceive function
_receive = opts.receive;
opts.receive = function(e, ui) {
onReceive(e, ui);
if (typeof _receive === "function")
_receive(e, ui);
};
// If user provided 'remove' callback compose it with onRemove function
_remove = opts.remove;
opts.remove = function(e, ui) {
onRemove(e, ui);
if (typeof _remove === "function")
_remove(e, ui);
};
}
// Create sortable
element.sortable(opts);
}
};
}
]);
/**
* Binds a TinyMCE widget to <textarea> elements.
*/
angular.module('ui.directives').directive('uiTinymce', ['ui.config', function (uiConfig) {
uiConfig.tinymce = uiConfig.tinymce || {};
return {
require: 'ngModel',
link: function (scope, elm, attrs, ngModel) {
var expression,
options = {
// Update model on button click
onchange_callback: function (inst) {
if (inst.isDirty()) {
inst.save();
ngModel.$setViewValue(elm.val());
if (!scope.$$phase)
scope.$apply();
}
},
// Update model on keypress
handle_event_callback: function (e) {
if (this.isDirty()) {
this.save();
ngModel.$setViewValue(elm.val());
if (!scope.$$phase)
scope.$apply();
}
return true; // Continue handling
},
// Update model when calling setContent (such as from the source editor popup)
setup: function (ed) {
ed.onSetContent.add(function (ed, o) {
if (ed.isDirty()) {
ed.save();
ngModel.$setViewValue(elm.val());
if (!scope.$$phase)
scope.$apply();
}
});
}
};
if (attrs.uiTinymce) {
expression = scope.$eval(attrs.uiTinymce);
} else {
expression = {};
}
angular.extend(options, uiConfig.tinymce, expression);
setTimeout(function () {
elm.tinymce(options);
});
}
};
}]);
/**
* General-purpose validator for ngModel.
* angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using
* an arbitrary validation function requires creation of a custom formatters and / or parsers.
* The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s).
* A validator function will trigger validation on both model and input changes.
*
* @example <input ui-validate=" 'myValidatorFunction($value)' ">
* @example <input ui-validate="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }">
* @example <input ui-validate="{ foo : '$value > anotherModel' }" ui-validate-watch=" 'anotherModel' ">
* @example <input ui-validate="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }" ui-validate-watch=" { foo : 'anotherModel' } ">
*
* @param ui-validate {string|object literal} If strings is passed it should be a scope's function to be used as a validator.
* If an object literal is passed a key denotes a validation error key while a value should be a validator function.
* In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result.
*/
angular.module('ui.directives').directive('uiValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var validateFn, watch, validators = {},
validateExpr = scope.$eval(attrs.uiValidate);
if (!validateExpr) return;
if (angular.isString(validateExpr)) {
validateExpr = { validator: validateExpr };
}
angular.forEach(validateExpr, function (expression, key) {
validateFn = function (valueToValidate) {
if (scope.$eval(expression, { '$value' : valueToValidate })) {
ctrl.$setValidity(key, true);
return valueToValidate;
} else {
ctrl.$setValidity(key, false);
return undefined;
}
};
validators[key] = validateFn;
ctrl.$formatters.push(validateFn);
ctrl.$parsers.push(validateFn);
});
// Support for ui-validate-watch
if (attrs.uiValidateWatch) {
watch = scope.$eval(attrs.uiValidateWatch);
if (angular.isString(watch)) {
scope.$watch(watch, function(){
angular.forEach(validators, function(validatorFn, key){
validatorFn(ctrl.$modelValue);
});
});
} else {
angular.forEach(watch, function(expression, key){
scope.$watch(expression, function(){
validators[key](ctrl.$modelValue);
});
});
}
}
}
};
});
/**
* A replacement utility for internationalization very similar to sprintf.
*
* @param replace {mixed} The tokens to replace depends on type
* string: all instances of $0 will be replaced
* array: each instance of $0, $1, $2 etc. will be placed with each array item in corresponding order
* object: all attributes will be iterated through, with :key being replaced with its corresponding value
* @return string
*
* @example: 'Hello :name, how are you :day'.format({ name:'John', day:'Today' })
* @example: 'Records $0 to $1 out of $2 total'.format(['10', '20', '3000'])
* @example: '$0 agrees to all mentions $0 makes in the event that $0 hits a tree while $0 is driving drunk'.format('Bob')
*/
angular.module('ui.filters').filter('format', function(){
return function(value, replace) {
if (!value) {
return value;
}
var target = value.toString(), token;
if (replace === undefined) {
return target;
}
if (!angular.isArray(replace) && !angular.isObject(replace)) {
return target.split('$0').join(replace);
}
token = angular.isArray(replace) && '$' || ':';
angular.forEach(replace, function(value, key){
target = target.split(token+key).join(value);
});
return target;
};
});
/**
* Wraps the
* @param text {string} haystack to search through
* @param search {string} needle to search for
* @param [caseSensitive] {boolean} optional boolean to use case-sensitive searching
*/
angular.module('ui.filters').filter('highlight', function () {
return function (text, search, caseSensitive) {
if (search || angular.isNumber(search)) {
text = text.toString();
search = search.toString();
if (caseSensitive) {
return text.split(search).join('<span class="ui-match">' + search + '</span>');
} else {
return text.replace(new RegExp(search, 'gi'), '<span class="ui-match">$&</span>');
}
} else {
return text;
}
};
});
/**
* Converts variable-esque naming conventions to something presentational, capitalized words separated by space.
* @param {String} value The value to be parsed and prettified.
* @param {String} [inflector] The inflector to use. Default: humanize.
* @return {String}
* @example {{ 'Here Is my_phoneNumber' | inflector:'humanize' }} => Here Is My Phone Number
* {{ 'Here Is my_phoneNumber' | inflector:'underscore' }} => here_is_my_phone_number
* {{ 'Here Is my_phoneNumber' | inflector:'variable' }} => hereIsMyPhoneNumber
*/
angular.module('ui.filters').filter('inflector', function () {
function ucwords(text) {
return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
return $1.toUpperCase();
});
}
function breakup(text, separator) {
return text.replace(/[A-Z]/g, function (match) {
return separator + match;
});
}
var inflectors = {
humanize: function (value) {
return ucwords(breakup(value, ' ').split('_').join(' '));
},
underscore: function (value) {
return value.substr(0, 1).toLowerCase() + breakup(value.substr(1), '_').toLowerCase().split(' ').join('_');
},
variable: function (value) {
value = value.substr(0, 1).toLowerCase() + ucwords(value.split('_').join(' ')).substr(1).split(' ').join('');
return value;
}
};
return function (text, inflector, separator) {
if (inflector !== false && angular.isString(text)) {
inflector = inflector || 'humanize';
return inflectors[inflector](text);
} else {
return text;
}
};
});
/**
* Filters out all duplicate items from an array by checking the specified key
* @param [key] {string} the name of the attribute of each object to compare for uniqueness
if the key is empty, the entire object will be compared
if the key === false then no filtering will be performed
* @return {array}
*/
angular.module('ui.filters').filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});
/**
* Created by kimardenmiller on 4/9/14.
*/
/*
Copyright 2012 Igor Vaynberg
Version: @@ver@@ Timestamp: @@timestamp@@
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your
use of this software only upon the condition that you accept all of the terms of either the Apache
License or the GPL License.
You may obtain a copy of the Apache License and the GPL License at:
http://www.apache.org/licenses/LICENSE-2.0
http://www.gnu.org/licenses/gpl-2.0.html
Unless required by applicable law or agreed to in writing, software distributed under the
Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
the specific language governing permissions and limitations under the Apache License and the GPL License.
*/
(function ($) {
if(typeof $.fn.each2 == "undefined") {
$.extend($.fn, {
/*
* 4-10 times faster .each replacement
* use it carefully, as it overrides jQuery context of element on each iteration
*/
each2 : function (c) {
var j = $([0]), i = -1, l = this.length;
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
);
return this;
}
});
}
})(jQuery);
(function ($, undefined) {
"use strict";
/*global document, window, jQuery, console */
if (window.Select2 !== undefined) {
return;
}
var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
KEY = {
TAB: 9,
ENTER: 13,
ESC: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
SHIFT: 16,
CTRL: 17,
ALT: 18,
PAGE_UP: 33,
PAGE_DOWN: 34,
HOME: 36,
END: 35,
BACKSPACE: 8,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
},
isControl: function (e) {
var k = e.which;
switch (k) {
case KEY.SHIFT:
case KEY.CTRL:
case KEY.ALT:
return true;
}
if (e.metaKey) return true;
return false;
},
isFunctionKey: function (k) {
k = k.which ? k.which : k;
return k >= 112 && k <= 123;
}
},
MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>",
DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z"};
$document = $(document);
nextUid=(function() { var counter=1; return function() { return counter++; }; }());
function reinsertElement(element) {
var placeholder = $(document.createTextNode(''));
element.before(placeholder);
placeholder.before(element);
placeholder.remove();
}
function stripDiacritics(str) {
var ret, i, l, c;
if (!str || str.length < 1) return str;
ret = "";
for (i = 0, l = str.length; i < l; i++) {
c = str.charAt(i);
ret += DIACRITICS[c] || c;
}
return ret;
}
function indexOf(value, array) {
var i = 0, l = array.length;
for (; i < l; i = i + 1) {
if (equal(value, array[i])) return i;
}
return -1;
}
function measureScrollbar () {
var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
$template.appendTo('body');
var dim = {
width: $template.width() - $template[0].clientWidth,
height: $template.height() - $template[0].clientHeight
};
$template.remove();
return dim;
}
/**
* Compares equality of a and b
* @param a
* @param b
*/
function equal(a, b) {
if (a === b) return true;
if (a === undefined || b === undefined) return false;
if (a === null || b === null) return false;
// Check whether 'a' or 'b' is a string (primitive or object).
// The concatenation of an empty string (+'') converts its argument to a string's primitive.
if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
return false;
}
/**
* Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
* strings
* @param string
* @param separator
*/
function splitVal(string, separator) {
var val, i, l;
if (string === null || string.length < 1) return [];
val = string.split(separator);
for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
return val;
}
function getSideBorderPadding(element) {
return element.outerWidth(false) - element.width();
}
function installKeyUpChangeEvent(element) {
var key="keyup-change-value";
element.on("keydown", function () {
if ($.data(element, key) === undefined) {
$.data(element, key, element.val());
}
});
element.on("keyup", function () {
var val= $.data(element, key);
if (val !== undefined && element.val() !== val) {
$.removeData(element, key);
element.trigger("keyup-change");
}
});
}
$document.on("mousemove", function (e) {
lastMousePosition.x = e.pageX;
lastMousePosition.y = e.pageY;
});
/**
* filters mouse events so an event is fired only if the mouse moved.
*
* filters out mouse events that occur when mouse is stationary but
* the elements under the pointer are scrolled.
*/
function installFilteredMouseMove(element) {
element.on("mousemove", function (e) {
var lastpos = lastMousePosition;
if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
$(e.target).trigger("mousemove-filtered", e);
}
});
}
/**
* Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
* within the last quietMillis milliseconds.
*
* @param quietMillis number of milliseconds to wait before invoking fn
* @param fn function to be debounced
* @param ctx object to be used as this reference within fn
* @return debounced version of fn
*/
function debounce(quietMillis, fn, ctx) {
ctx = ctx || undefined;
var timeout;
return function () {
var args = arguments;
window.clearTimeout(timeout);
timeout = window.setTimeout(function() {
fn.apply(ctx, args);
}, quietMillis);
};
}
/**
* A simple implementation of a thunk
* @param formula function used to lazily initialize the thunk
* @return {Function}
*/
function thunk(formula) {
var evaluated = false,
value;
return function() {
if (evaluated === false) { value = formula(); evaluated = true; }
return value;
};
};
function installDebouncedScroll(threshold, element) {
var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
element.on("scroll", function (e) {
if (indexOf(e.target, element.get()) >= 0) notify(e);
});
}
function focus($el) {
if ($el[0] === document.activeElement) return;
/* set the focus in a 0 timeout - that way the focus is set after the processing
of the current event has finished - which seems like the only reliable way
to set focus */
window.setTimeout(function() {
var el=$el[0], pos=$el.val().length, range;
$el.focus();
/* make sure el received focus so we do not error out when trying to manipulate the caret.
sometimes modals or others listeners may steal it after its set */
var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
if (isVisible && el === document.activeElement) {
/* after the focus is set move the caret to the end, necessary when we val()
just before setting focus */
if(el.setSelectionRange)
{
el.setSelectionRange(pos, pos);
}
else if (el.createTextRange) {
range = el.createTextRange();
range.collapse(false);
range.select();
}
}
}, 0);
}
function getCursorInfo(el) {
el = $(el)[0];
var offset = 0;
var length = 0;
if ('selectionStart' in el) {
offset = el.selectionStart;
length = el.selectionEnd - offset;
} else if ('selection' in document) {
el.focus();
var sel = document.selection.createRange();
length = document.selection.createRange().text.length;
sel.moveStart('character', -el.value.length);
offset = sel.text.length - length;
}
return { offset: offset, length: length };
}
function killEvent(event) {
event.preventDefault();
event.stopPropagation();
}
function killEventImmediately(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
function measureTextWidth(e) {
if (!sizer){
var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
sizer = $(document.createElement("div")).css({
position: "absolute",
left: "-10000px",
top: "-10000px",
display: "none",
fontSize: style.fontSize,
fontFamily: style.fontFamily,
fontStyle: style.fontStyle,
fontWeight: style.fontWeight,
letterSpacing: style.letterSpacing,
textTransform: style.textTransform,
whiteSpace: "nowrap"
});
sizer.attr("class","select2-sizer");
$("body").append(sizer);
}
sizer.text(e.val());
return sizer.width();
}
function syncCssClasses(dest, src, adapter) {
var classes, replacements = [], adapted;
classes = dest.attr("class");
if (classes) {
classes = '' + classes; // for IE which returns object
$(classes.split(" ")).each2(function() {
if (this.indexOf("select2-") === 0) {
replacements.push(this);
}
});
}
classes = src.attr("class");
if (classes) {
classes = '' + classes; // for IE which returns object
$(classes.split(" ")).each2(function() {
if (this.indexOf("select2-") !== 0) {
adapted = adapter(this);
if (adapted) {
replacements.push(adapted);
}
}
});
}
dest.attr("class", replacements.join(" "));
}
function markMatch(text, term, markup, escapeMarkup) {
var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
tl=term.length;
if (match<0) {
markup.push(escapeMarkup(text));
return;
}
markup.push(escapeMarkup(text.substring(0, match)));
markup.push("<span class='select2-match'>");
markup.push(escapeMarkup(text.substring(match, match + tl)));
markup.push("</span>");
markup.push(escapeMarkup(text.substring(match + tl, text.length)));
}
function defaultEscapeMarkup(markup) {
var replace_map = {
'\\': '&#92;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
"/": '&#47;'
};
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
return replace_map[match];
});
}
/**
* Produces an ajax-based query function
*
* @param options object containing configuration parameters
* @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
* @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
* @param options.url url for the data
* @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
* @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
* @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
* @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
* The expected format is an object containing the following keys:
* results array of objects that will be used as choices
* more (optional) boolean indicating whether there are more results available
* Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
*/
function ajax(options) {
var timeout, // current scheduled but not yet executed request
handler = null,
quietMillis = options.quietMillis || 100,
ajaxUrl = options.url,
self = this;
return function (query) {
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
var data = options.data, // ajax data function
url = ajaxUrl, // ajax url string or function
transport = options.transport || $.fn.select2.ajaxDefaults.transport,
// deprecated - to be removed in 4.0 - use params instead
deprecated = {
type: options.type || 'GET', // set type of request (GET or POST)
cache: options.cache || false,
jsonpCallback: options.jsonpCallback||undefined,
dataType: options.dataType||"json"
},
params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
data = data ? data.call(self, query.term, query.page, query.context) : null;
url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
if (handler && typeof handler.abort === "function") { handler.abort(); }
if (options.params) {
if ($.isFunction(options.params)) {
$.extend(params, options.params.call(self));
} else {
$.extend(params, options.params);
}
}
$.extend(params, {
url: url,
dataType: options.dataType,
data: data,
success: function (data) {
// TODO - replace query.page with query so users have access to term, page, etc.
var results = options.results(data, query.page);
query.callback(results);
}
});
handler = transport.call(self, params);
}, quietMillis);
};
}
/**
* Produces a query function that works with a local array
*
* @param options object containing configuration parameters. The options parameter can either be an array or an
* object.
*
* If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
*
* If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
* an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
* key can either be a String in which case it is expected that each element in the 'data' array has a key with the
* value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
* the text.
*/
function local(options) {
var data = options, // data elements
dataText,
tmp,
text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
if ($.isArray(data)) {
tmp = data;
data = { results: tmp };
}
if ($.isFunction(data) === false) {
tmp = data;
data = function() { return tmp; };
}
var dataItem = data();
if (dataItem.text) {
text = dataItem.text;
// if text is not a function we assume it to be a key name
if (!$.isFunction(text)) {
dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
text = function (item) { return item[dataText]; };
}
}
return function (query) {
var t = query.term, filtered = { results: [] }, process;
if (t === "") {
query.callback(data());
return;
}
process = function(datum, collection) {
var group, attr;
datum = datum[0];
if (datum.children) {
group = {};
for (attr in datum) {
if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
}
group.children=[];
$(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
if (group.children.length || query.matcher(t, text(group), datum)) {
collection.push(group);
}
} else {
if (query.matcher(t, text(datum), datum)) {
collection.push(datum);
}
}
};
$(data().results).each2(function(i, datum) { process(datum, filtered.results); });
query.callback(filtered);
};
}
// TODO javadoc
function tags(data) {
var isFunc = $.isFunction(data);
return function (query) {
var t = query.term, filtered = {results: []};
$(isFunc ? data() : data).each(function () {
var isObject = this.text !== undefined,
text = isObject ? this.text : this;
if (t === "" || query.matcher(t, text)) {
filtered.results.push(isObject ? this : {id: this, text: this});
}
});
query.callback(filtered);
};
}
/**
* Checks if the formatter function should be used.
*
* Throws an error if it is not a function. Returns true if it should be used,
* false if no formatting should be performed.
*
* @param formatter
*/
function checkFormatter(formatter, formatterName) {
if ($.isFunction(formatter)) return true;
if (!formatter) return false;
if (typeof(formatter) === 'string') return true;
throw new Error(formatterName +" must be a string, function, or falsy value");
}
function evaluate(val) {
if ($.isFunction(val)) {
var args = Array.prototype.slice.call(arguments, 1);
return val.apply(null, args);
}
return val;
}
function countResults(results) {
var count = 0;
$.each(results, function(i, item) {
if (item.children) {
count += countResults(item.children);
} else {
count++;
}
});
return count;
}
/**
* Default tokenizer. This function uses breaks the input on substring match of any string from the
* opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
* two options have to be defined in order for the tokenizer to work.
*
* @param input text user has typed so far or pasted into the search field
* @param selection currently selected choices
* @param selectCallback function(choice) callback tho add the choice to selection
* @param opts select2's opts
* @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
*/
function defaultTokenizer(input, selection, selectCallback, opts) {
var original = input, // store the original so we can compare and know if we need to tell the search to update its text
dupe = false, // check for whether a token we extracted represents a duplicate selected choice
token, // token
index, // position at which the separator was found
i, l, // looping variables
separator; // the matched separator
if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
while (true) {
index = -1;
for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
separator = opts.tokenSeparators[i];
index = input.indexOf(separator);
if (index >= 0) break;
}
if (index < 0) break; // did not find any token separator in the input string, bail
token = input.substring(0, index);
input = input.substring(index + separator.length);
if (token.length > 0) {
token = opts.createSearchChoice.call(this, token, selection);
if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
dupe = false;
for (i = 0, l = selection.length; i < l; i++) {
if (equal(opts.id(token), opts.id(selection[i]))) {
dupe = true; break;
}
}
if (!dupe) selectCallback(token);
}
}
}
if (original!==input) return input;
}
/**
* Creates a new class
*
* @param superClass
* @param methods
*/
function clazz(SuperClass, methods) {
var constructor = function () {};
constructor.prototype = new SuperClass;
constructor.prototype.constructor = constructor;
constructor.prototype.parent = SuperClass.prototype;
constructor.prototype = $.extend(constructor.prototype, methods);
return constructor;
}
AbstractSelect2 = clazz(Object, {
// abstract
bind: function (func) {
var self = this;
return function () {
func.apply(self, arguments);
};
},
// abstract
init: function (opts) {
var results, search, resultsSelector = ".select2-results";
// prepare options
this.opts = opts = this.prepareOpts(opts);
this.id=opts.id;
// destroy if called on an existing component
if (opts.element.data("select2") !== undefined &&
opts.element.data("select2") !== null) {
opts.element.data("select2").destroy();
}
this.container = this.createContainer();
this.liveRegion = $("<span>", {
role: "status",
"aria-live": "polite"
})
.addClass("select2-hidden-accessible")
.appendTo(document.body);
this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()).replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
this.containerSelector="#"+this.containerId;
this.container.attr("id", this.containerId);
// cache the body so future lookups are cheap
this.body = thunk(function() { return opts.element.closest("body"); });
syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
this.container.attr("style", opts.element.attr("style"));
this.container.css(evaluate(opts.containerCss));
this.container.addClass(evaluate(opts.containerCssClass));
this.elementTabIndex = this.opts.element.attr("tabindex");
// swap container for the element
this.opts.element
.data("select2", this)
.attr("tabindex", "-1")
.before(this.container)
.on("click.select2", killEvent); // do not leak click events
this.container.data("select2", this);
this.dropdown = this.container.find(".select2-drop");
syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
this.dropdown.addClass(evaluate(opts.dropdownCssClass));
this.dropdown.data("select2", this);
this.dropdown.on("click", killEvent);
this.results = results = this.container.find(resultsSelector);
this.search = search = this.container.find("input.select2-input");
this.queryCount = 0;
this.resultsPage = 0;
this.context = null;
// initialize the container
this.initContainer();
this.container.on("click", killEvent);
installFilteredMouseMove(this.results);
this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
this._touchEvent = true;
this.highlightUnderEvent(event);
}));
this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
// Waiting for a click event on touch devices to select option and hide dropdown
// otherwise click will be triggered on an underlying element
this.dropdown.on('click', this.bind(function (event) {
if (this._touchEvent) {
this._touchEvent = false;
this.selectHighlighted();
}
}));
installDebouncedScroll(80, this.results);
this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
// do not propagate change event from the search field out of the component
$(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
$(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
// if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
if ($.fn.mousewheel) {
results.mousewheel(function (e, delta, deltaX, deltaY) {
var top = results.scrollTop();
if (deltaY > 0 && top - deltaY <= 0) {
results.scrollTop(0);
killEvent(e);
} else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
results.scrollTop(results.get(0).scrollHeight - results.height());
killEvent(e);
}
});
}
installKeyUpChangeEvent(search);
search.on("keyup-change input paste", this.bind(this.updateResults));
search.on("focus", function () { search.addClass("select2-focused"); });
search.on("blur", function () { search.removeClass("select2-focused");});
this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
if ($(e.target).closest(".select2-result-selectable").length > 0) {
this.highlightUnderEvent(e);
this.selectHighlighted(e);
}
}));
// trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
// for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
// dom it will trigger the popup close, which is not what we want
// focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
this.dropdown.on("click mouseup mousedown focusin", function (e) { e.stopPropagation(); });
this.nextSearchTerm = undefined;
if ($.isFunction(this.opts.initSelection)) {
// initialize selection based on the current value of the source element
this.initSelection();
// if the user has provided a function that can set selection based on the value of the source element
// we monitor the change event on the element and trigger it, allowing for two way synchronization
this.monitorSource();
}
if (opts.maximumInputLength !== null) {
this.search.attr("maxlength", opts.maximumInputLength);
}
var disabled = opts.element.prop("disabled");
if (disabled === undefined) disabled = false;
this.enable(!disabled);
var readonly = opts.element.prop("readonly");
if (readonly === undefined) readonly = false;
this.readonly(readonly);
// Calculate size of scrollbar
scrollBarDimensions = scrollBarDimensions || measureScrollbar();
this.autofocus = opts.element.prop("autofocus");
opts.element.prop("autofocus", false);
if (this.autofocus) this.focus();
this.search.attr("placeholder", opts.searchInputPlaceholder);
},
// abstract
destroy: function () {
var element=this.opts.element, select2 = element.data("select2");
this.close();
if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
if (select2 !== undefined) {
select2.container.remove();
select2.liveRegion.remove();
select2.dropdown.remove();
element
.removeClass("select2-offscreen")
.removeData("select2")
.off(".select2")
.prop("autofocus", this.autofocus || false);
if (this.elementTabIndex) {
element.attr({tabindex: this.elementTabIndex});
} else {
element.removeAttr("tabindex");
}
element.show();
}
},
// abstract
optionToData: function(element) {
if (element.is("option")) {
return {
id:element.prop("value"),
text:element.text(),
element: element.get(),
css: element.attr("class"),
disabled: element.prop("disabled"),
locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
};
} else if (element.is("optgroup")) {
return {
text:element.attr("label"),
children:[],
element: element.get(),
css: element.attr("class")
};
}
},
// abstract
prepareOpts: function (opts) {
var element, select, idKey, ajaxUrl, self = this;
element = opts.element;
if (element.get(0).tagName.toLowerCase() === "select") {
this.select = select = opts.element;
}
if (select) {
// these options are not allowed when attached to a select because they are picked up off the element itself
$.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
if (this in opts) {
throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
}
});
}
opts = $.extend({}, {
populateResults: function(container, results, query) {
var populate, id=this.opts.id, liveRegion=this.liveRegion;
populate=function(results, container, depth) {
var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
results = opts.sortResults(results, container, query);
for (i = 0, l = results.length; i < l; i = i + 1) {
result=results[i];
disabled = (result.disabled === true);
selectable = (!disabled) && (id(result) !== undefined);
compound=result.children && result.children.length > 0;
node=$("<li></li>");
node.addClass("select2-results-dept-"+depth);
node.addClass("select2-result");
node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
if (disabled) { node.addClass("select2-disabled"); }
if (compound) { node.addClass("select2-result-with-children"); }
node.addClass(self.opts.formatResultCssClass(result));
node.attr("role", "presentation");
label=$(document.createElement("div"));
label.addClass("select2-result-label");
label.attr("id", "select2-result-label-" + nextUid());
label.attr("role", "option");
formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
if (formatted!==undefined) {
label.html(formatted);
node.append(label);
}
if (compound) {
innerContainer=$("<ul></ul>");
innerContainer.addClass("select2-result-sub");
populate(result.children, innerContainer, depth+1);
node.append(innerContainer);
}
node.data("select2-data", result);
container.append(node);
}
liveRegion.text(opts.formatMatches(results.length));
};
populate(results, container, 0);
}
}, $.fn.select2.defaults, opts);
if (typeof(opts.id) !== "function") {
idKey = opts.id;
opts.id = function (e) { return e[idKey]; };
}
if ($.isArray(opts.element.data("select2Tags"))) {
if ("tags" in opts) {
throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
}
opts.tags=opts.element.data("select2Tags");
}
if (select) {
opts.query = this.bind(function (query) {
var data = { results: [], more: false },
term = query.term,
children, placeholderOption, process;
process=function(element, collection) {
var group;
if (element.is("option")) {
if (query.matcher(term, element.text(), element)) {
collection.push(self.optionToData(element));
}
} else if (element.is("optgroup")) {
group=self.optionToData(element);
element.children().each2(function(i, elm) { process(elm, group.children); });
if (group.children.length>0) {
collection.push(group);
}
}
};
children=element.children();
// ignore the placeholder option if there is one
if (this.getPlaceholder() !== undefined && children.length > 0) {
placeholderOption = this.getPlaceholderOption();
if (placeholderOption) {
children=children.not(placeholderOption);
}
}
children.each2(function(i, elm) { process(elm, data.results); });
query.callback(data);
});
// this is needed because inside val() we construct choices from options and there id is hardcoded
opts.id=function(e) { return e.id; };
} else {
if (!("query" in opts)) {
if ("ajax" in opts) {
ajaxUrl = opts.element.data("ajax-url");
if (ajaxUrl && ajaxUrl.length > 0) {
opts.ajax.url = ajaxUrl;
}
opts.query = ajax.call(opts.element, opts.ajax);
} else if ("data" in opts) {
opts.query = local(opts.data);
} else if ("tags" in opts) {
opts.query = tags(opts.tags);
if (opts.createSearchChoice === undefined) {
opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
}
if (opts.initSelection === undefined) {
opts.initSelection = function (element, callback) {
var data = [];
$(splitVal(element.val(), opts.separator)).each(function () {
var obj = { id: this, text: this },
tags = opts.tags;
if ($.isFunction(tags)) tags=tags();
$(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
data.push(obj);
});
callback(data);
};
}
}
}
}
if (typeof(opts.query) !== "function") {
throw "query function not defined for Select2 " + opts.element.attr("id");
}
if (opts.createSearchChoicePosition === 'top') {
opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
}
else if (opts.createSearchChoicePosition === 'bottom') {
opts.createSearchChoicePosition = function(list, item) { list.push(item); };
}
else if (typeof(opts.createSearchChoicePosition) !== "function") {
throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
}
return opts;
},
/**
* Monitor the original element for changes and update select2 accordingly
*/
// abstract
monitorSource: function () {
var el = this.opts.element, sync, observer;
el.on("change.select2", this.bind(function (e) {
if (this.opts.element.data("select2-change-triggered") !== true) {
this.initSelection();
}
}));
sync = this.bind(function () {
// sync enabled state
var disabled = el.prop("disabled");
if (disabled === undefined) disabled = false;
this.enable(!disabled);
var readonly = el.prop("readonly");
if (readonly === undefined) readonly = false;
this.readonly(readonly);
syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
this.container.addClass(evaluate(this.opts.containerCssClass));
syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
this.dropdown.addClass(evaluate(this.opts.dropdownCssClass));
});
// IE8-10
el.on("propertychange.select2", sync);
// hold onto a reference of the callback to work around a chromium bug
if (this.mutationCallback === undefined) {
this.mutationCallback = function (mutations) {
mutations.forEach(sync);
}
}
// safari, chrome, firefox, IE11
observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
if (observer !== undefined) {
if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
this.propertyObserver = new observer(this.mutationCallback);
this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
}
},
// abstract
triggerSelect: function(data) {
var evt = $.Event("select2-selecting", { val: this.id(data), object: data });
this.opts.element.trigger(evt);
return !evt.isDefaultPrevented();
},
/**
* Triggers the change event on the source element
*/
// abstract
triggerChange: function (details) {
details = details || {};
details= $.extend({}, details, { type: "change", val: this.val() });
// prevents recursive triggering
this.opts.element.data("select2-change-triggered", true);
this.opts.element.trigger(details);
this.opts.element.data("select2-change-triggered", false);
// some validation frameworks ignore the change event and listen instead to keyup, click for selects
// so here we trigger the click event manually
this.opts.element.click();
// ValidationEngine ignores the change event and listens instead to blur
// so here we trigger the blur event manually if so desired
if (this.opts.blurOnChange)
this.opts.element.blur();
},
//abstract
isInterfaceEnabled: function()
{
return this.enabledInterface === true;
},
// abstract
enableInterface: function() {
var enabled = this._enabled && !this._readonly,
disabled = !enabled;
if (enabled === this.enabledInterface) return false;
this.container.toggleClass("select2-container-disabled", disabled);
this.close();
this.enabledInterface = enabled;
return true;
},
// abstract
enable: function(enabled) {
if (enabled === undefined) enabled = true;
if (this._enabled === enabled) return;
this._enabled = enabled;
this.opts.element.prop("disabled", !enabled);
this.enableInterface();
},
// abstract
disable: function() {
this.enable(false);
},
// abstract
readonly: function(enabled) {
if (enabled === undefined) enabled = false;
if (this._readonly === enabled) return;
this._readonly = enabled;
this.opts.element.prop("readonly", enabled);
this.enableInterface();
},
// abstract
opened: function () {
return this.container.hasClass("select2-dropdown-open");
},
// abstract
positionDropdown: function() {
var $dropdown = this.dropdown,
offset = this.container.offset(),
height = this.container.outerHeight(false),
width = this.container.outerWidth(false),
dropHeight = $dropdown.outerHeight(false),
$window = $(window),
windowWidth = $window.width(),
windowHeight = $window.height(),
viewPortRight = $window.scrollLeft() + windowWidth,
viewportBottom = $window.scrollTop() + windowHeight,
dropTop = offset.top + height,
dropLeft = offset.left,
enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
dropWidth = $dropdown.outerWidth(false),
enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
aboveNow = $dropdown.hasClass("select2-drop-above"),
bodyOffset,
above,
changeDirection,
css,
resultsListNode;
// always prefer the current above/below alignment, unless there is not enough room
if (aboveNow) {
above = true;
if (!enoughRoomAbove && enoughRoomBelow) {
changeDirection = true;
above = false;
}
} else {
above = false;
if (!enoughRoomBelow && enoughRoomAbove) {
changeDirection = true;
above = true;
}
}
//if we are changing direction we need to get positions when dropdown is hidden;
if (changeDirection) {
$dropdown.hide();
offset = this.container.offset();
height = this.container.outerHeight(false);
width = this.container.outerWidth(false);
dropHeight = $dropdown.outerHeight(false);
viewPortRight = $window.scrollLeft() + windowWidth;
viewportBottom = $window.scrollTop() + windowHeight;
dropTop = offset.top + height;
dropLeft = offset.left;
dropWidth = $dropdown.outerWidth(false);
enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
$dropdown.show();
// fix so the cursor does not move to the left within the search-textbox in IE
this.focusSearch();
}
if (this.opts.dropdownAutoWidth) {
resultsListNode = $('.select2-results', $dropdown)[0];
$dropdown.addClass('select2-drop-auto-width');
$dropdown.css('width', '');
// Add scrollbar width to dropdown if vertical scrollbar is present
dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
dropWidth > width ? width = dropWidth : dropWidth = width;
enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
}
else {
this.container.removeClass('select2-drop-auto-width');
}
//console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
//console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
// fix positioning when body has an offset and is not position: static
if (this.body().css('position') !== 'static') {
bodyOffset = this.body().offset();
dropTop -= bodyOffset.top;
dropLeft -= bodyOffset.left;
}
if (!enoughRoomOnRight) {
dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
}
css = {
left: dropLeft,
width: width
};
if (above) {
css.top = offset.top - dropHeight;
css.bottom = 'auto';
this.container.addClass("select2-drop-above");
$dropdown.addClass("select2-drop-above");
}
else {
css.top = dropTop;
css.bottom = 'auto';
this.container.removeClass("select2-drop-above");
$dropdown.removeClass("select2-drop-above");
}
css = $.extend(css, evaluate(this.opts.dropdownCss));
$dropdown.css(css);
},
// abstract
shouldOpen: function() {
var event;
if (this.opened()) return false;
if (this._enabled === false || this._readonly === true) return false;
event = $.Event("select2-opening");
this.opts.element.trigger(event);
return !event.isDefaultPrevented();
},
// abstract
clearDropdownAlignmentPreference: function() {
// clear the classes used to figure out the preference of where the dropdown should be opened
this.container.removeClass("select2-drop-above");
this.dropdown.removeClass("select2-drop-above");
},
/**
* Opens the dropdown
*
* @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
* the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
*/
// abstract
open: function () {
if (!this.shouldOpen()) return false;
this.opening();
return true;
},
/**
* Performs the opening of the dropdown
*/
// abstract
opening: function() {
var cid = this.containerId,
scroll = "scroll." + cid,
resize = "resize."+cid,
orient = "orientationchange."+cid,
mask;
this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
this.clearDropdownAlignmentPreference();
if(this.dropdown[0] !== this.body().children().last()[0]) {
this.dropdown.detach().appendTo(this.body());
}
// create the dropdown mask if doesn't already exist
mask = $("#select2-drop-mask");
if (mask.length == 0) {
mask = $(document.createElement("div"));
mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
mask.hide();
mask.appendTo(this.body());
mask.on("mousedown touchstart click", function (e) {
// Prevent IE from generating a click event on the body
reinsertElement(mask);
var dropdown = $("#select2-drop"), self;
if (dropdown.length > 0) {
self=dropdown.data("select2");
if (self.opts.selectOnBlur) {
self.selectHighlighted({noFocus: true});
}
self.close();
e.preventDefault();
e.stopPropagation();
}
});
}
// ensure the mask is always right before the dropdown
if (this.dropdown.prev()[0] !== mask[0]) {
this.dropdown.before(mask);
}
// move the global id to the correct dropdown
$("#select2-drop").removeAttr("id");
this.dropdown.attr("id", "select2-drop");
// show the elements
mask.show();
this.positionDropdown();
this.dropdown.show();
this.positionDropdown();
this.dropdown.addClass("select2-drop-active");
// attach listeners to events that can change the position of the container and thus require
// the position of the dropdown to be updated as well so it does not come unglued from the container
var that = this;
this.container.parents().add(window).each(function () {
$(this).on(resize+" "+scroll+" "+orient, function (e) {
that.positionDropdown();
});
});
},
// abstract
close: function () {
if (!this.opened()) return;
var cid = this.containerId,
scroll = "scroll." + cid,
resize = "resize."+cid,
orient = "orientationchange."+cid;
// unbind event listeners
this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
this.clearDropdownAlignmentPreference();
$("#select2-drop-mask").hide();
this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
this.dropdown.hide();
this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
this.results.empty();
this.clearSearch();
this.search.removeClass("select2-active");
this.opts.element.trigger($.Event("select2-close"));
},
/**
* Opens control, sets input value, and updates results.
*/
// abstract
externalSearch: function (term) {
this.open();
this.search.val(term);
this.updateResults(false);
},
// abstract
clearSearch: function () {
},
//abstract
getMaximumSelectionSize: function() {
return evaluate(this.opts.maximumSelectionSize);
},
// abstract
ensureHighlightVisible: function () {
var results = this.results, children, index, child, hb, rb, y, more;
index = this.highlight();
if (index < 0) return;
if (index == 0) {
// if the first element is highlighted scroll all the way to the top,
// that way any unselectable headers above it will also be scrolled
// into view
results.scrollTop(0);
return;
}
children = this.findHighlightableChoices().find('.select2-result-label');
child = $(children[index]);
hb = child.offset().top + child.outerHeight(true);
// if this is the last child lets also make sure select2-more-results is visible
if (index === children.length - 1) {
more = results.find("li.select2-more-results");
if (more.length > 0) {
hb = more.offset().top + more.outerHeight(true);
}
}
rb = results.offset().top + results.outerHeight(true);
if (hb > rb) {
results.scrollTop(results.scrollTop() + (hb - rb));
}
y = child.offset().top - results.offset().top;
// make sure the top of the element is visible
if (y < 0 && child.css('display') != 'none' ) {
results.scrollTop(results.scrollTop() + y); // y is negative
}
},
// abstract
findHighlightableChoices: function() {
return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
},
// abstract
moveHighlight: function (delta) {
var choices = this.findHighlightableChoices(),
index = this.highlight();
while (index > -1 && index < choices.length) {
index += delta;
var choice = $(choices[index]);
if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
this.highlight(index);
break;
}
}
},
// abstract
highlight: function (index) {
var choices = this.findHighlightableChoices(),
choice,
data;
if (arguments.length === 0) {
return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
}
if (index >= choices.length) index = choices.length - 1;
if (index < 0) index = 0;
this.removeHighlight();
choice = $(choices[index]);
choice.addClass("select2-highlighted");
// ensure assistive technology can determine the active choice
this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
this.ensureHighlightVisible();
this.liveRegion.text(choice.text());
data = choice.data("select2-data");
if (data) {
this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
}
},
removeHighlight: function() {
this.results.find(".select2-highlighted").removeClass("select2-highlighted");
},
touchMoved: function() {
this._touchMoved = true;
},
clearTouchMoved: function() {
this._touchMoved = false;
},
// abstract
countSelectableResults: function() {
return this.findHighlightableChoices().length;
},
// abstract
highlightUnderEvent: function (event) {
var el = $(event.target).closest(".select2-result-selectable");
if (el.length > 0 && !el.is(".select2-highlighted")) {
var choices = this.findHighlightableChoices();
this.highlight(choices.index(el));
} else if (el.length == 0) {
// if we are over an unselectable item remove all highlights
this.removeHighlight();
}
},
// abstract
loadMoreIfNeeded: function () {
var results = this.results,
more = results.find("li.select2-more-results"),
below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
page = this.resultsPage + 1,
self=this,
term=this.search.val(),
context=this.context;
if (more.length === 0) return;
below = more.offset().top - results.offset().top - results.height();
if (below <= this.opts.loadMorePadding) {
more.addClass("select2-active");
this.opts.query({
element: this.opts.element,
term: term,
page: page,
context: context,
matcher: this.opts.matcher,
callback: this.bind(function (data) {
// ignore a response if the select2 has been closed before it was received
if (!self.opened()) return;
self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
self.postprocessResults(data, false, false);
if (data.more===true) {
more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, page+1));
window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
} else {
more.remove();
}
self.positionDropdown();
self.resultsPage = page;
self.context = data.context;
this.opts.element.trigger({ type: "select2-loaded", items: data });
})});
}
},
/**
* Default tokenizer function which does nothing
*/
tokenize: function() {
},
/**
* @param initial whether or not this is the call to this method right after the dropdown has been opened
*/
// abstract
updateResults: function (initial) {
var search = this.search,
results = this.results,
opts = this.opts,
data,
self = this,
input,
term = search.val(),
lastTerm = $.data(this.container, "select2-last-term"),
// sequence number used to drop out-of-order responses
queryNumber;
// prevent duplicate queries against the same term
if (initial !== true && lastTerm && equal(term, lastTerm)) return;
$.data(this.container, "select2-last-term", term);
// if the search is currently hidden we do not alter the results
if (initial !== true && (this.showSearchInput === false || !this.opened())) {
return;
}
function postRender() {
search.removeClass("select2-active");
self.positionDropdown();
if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
self.liveRegion.text(results.text());
}
else {
self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length));
}
}
function render(html) {
results.html(html);
postRender();
}
queryNumber = ++this.queryCount;
var maxSelSize = this.getMaximumSelectionSize();
if (maxSelSize >=1) {
data = this.data();
if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, maxSelSize) + "</li>");
return;
}
}
if (search.val().length < opts.minimumInputLength) {
if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, search.val(), opts.minimumInputLength) + "</li>");
} else {
render("");
}
if (initial && this.showSearch) this.showSearch(true);
return;
}
if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, search.val(), opts.maximumInputLength) + "</li>");
} else {
render("");
}
return;
}
if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
render("<li class='select2-searching'>" + evaluate(opts.formatSearching) + "</li>");
}
search.addClass("select2-active");
this.removeHighlight();
// give the tokenizer a chance to pre-process the input
input = this.tokenize();
if (input != undefined && input != null) {
search.val(input);
}
this.resultsPage = 1;
opts.query({
element: opts.element,
term: search.val(),
page: this.resultsPage,
context: null,
matcher: opts.matcher,
callback: this.bind(function (data) {
var def; // default choice
// ignore old responses
if (queryNumber != this.queryCount) {
return;
}
// ignore a response if the select2 has been closed before it was received
if (!this.opened()) {
this.search.removeClass("select2-active");
return;
}
// save context, if any
this.context = (data.context===undefined) ? null : data.context;
// create a default choice and prepend it to the list
if (this.opts.createSearchChoice && search.val() !== "") {
def = this.opts.createSearchChoice.call(self, search.val(), data.results);
if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
if ($(data.results).filter(
function () {
return equal(self.id(this), self.id(def));
}).length === 0) {
this.opts.createSearchChoicePosition(data.results, def);
}
}
}
if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, search.val()) + "</li>");
return;
}
results.empty();
self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(evaluate(opts.formatLoadMore, this.resultsPage)) + "</li>");
window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
}
this.postprocessResults(data, initial);
postRender();
this.opts.element.trigger({ type: "select2-loaded", items: data });
})});
},
// abstract
cancel: function () {
this.close();
},
// abstract
blur: function () {
// if selectOnBlur == true, select the currently highlighted option
if (this.opts.selectOnBlur)
this.selectHighlighted({noFocus: true});
this.close();
this.container.removeClass("select2-container-active");
// synonymous to .is(':focus'), which is available in jquery >= 1.6
if (this.search[0] === document.activeElement) { this.search.blur(); }
this.clearSearch();
this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
},
// abstract
focusSearch: function () {
focus(this.search);
},
// abstract
selectHighlighted: function (options) {
if (this._touchMoved) {
this.clearTouchMoved();
return;
}
var index=this.highlight(),
highlighted=this.results.find(".select2-highlighted"),
data = highlighted.closest('.select2-result').data("select2-data");
if (data) {
this.highlight(index);
this.onSelect(data, options);
} else if (options && options.noFocus) {
this.close();
}
},
// abstract
getPlaceholder: function () {
var placeholderOption;
return this.opts.element.attr("placeholder") ||
this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
this.opts.element.data("placeholder") ||
this.opts.placeholder ||
((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
},
// abstract
getPlaceholderOption: function() {
if (this.select) {
var firstOption = this.select.children('option').first();
if (this.opts.placeholderOption !== undefined ) {
//Determine the placeholder option based on the specified placeholderOption setting
return (this.opts.placeholderOption === "first" && firstOption) ||
(typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
} else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
//No explicit placeholder option specified, use the first if it's blank
return firstOption;
}
}
},
/**
* Get the desired width for the container element. This is
* derived first from option `width` passed to select2, then
* the inline 'style' on the original element, and finally
* falls back to the jQuery calculated element width.
*/
// abstract
initContainerWidth: function () {
function resolveContainerWidth() {
var style, attrs, matches, i, l, attr;
if (this.opts.width === "off") {
return null;
} else if (this.opts.width === "element"){
return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
} else if (this.opts.width === "copy" || this.opts.width === "resolve") {
// check if there is inline style on the element that contains width
style = this.opts.element.attr('style');
if (style !== undefined) {
attrs = style.split(';');
for (i = 0, l = attrs.length; i < l; i = i + 1) {
attr = attrs[i].replace(/\s/g, '');
matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
if (matches !== null && matches.length >= 1)
return matches[1];
}
}
if (this.opts.width === "resolve") {
// next check if css('width') can resolve a width that is percent based, this is sometimes possible
// when attached to input type=hidden or elements hidden via css
style = this.opts.element.css('width');
if (style.indexOf("%") > 0) return style;
// finally, fallback on the calculated width of the element
return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
}
return null;
} else if ($.isFunction(this.opts.width)) {
return this.opts.width();
} else {
return this.opts.width;
}
};
var width = resolveContainerWidth.call(this);
if (width !== null) {
this.container.css("width", width);
}
}
});
SingleSelect2 = clazz(AbstractSelect2, {
// single
createContainer: function () {
var container = $(document.createElement("div")).attr({
"class": "select2-container"
}).html([
"<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
" <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
" <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
"</a>",
"<label for='' class='select2-offscreen'></label>",
"<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
"<div class='select2-drop select2-display-none'>",
" <div class='select2-search'>",
" <label for='' class='select2-offscreen'></label>",
" <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
" aria-autocomplete='list' />",
" </div>",
" <ul class='select2-results' role='listbox'>",
" </ul>",
"</div>"].join(""));
return container;
},
// single
enableInterface: function() {
if (this.parent.enableInterface.apply(this, arguments)) {
this.focusser.prop("disabled", !this.isInterfaceEnabled());
}
},
// single
opening: function () {
var el, range, len;
if (this.opts.minimumResultsForSearch >= 0) {
this.showSearch(true);
}
this.parent.opening.apply(this, arguments);
if (this.showSearchInput !== false) {
// IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
// all other browsers handle this just fine
this.search.val(this.focusser.val());
}
if (this.opts.shouldFocusInput(this)) {
this.search.focus();
// move the cursor to the end after focussing, otherwise it will be at the beginning and
// new text will appear *before* focusser.val()
el = this.search.get(0);
if (el.createTextRange) {
range = el.createTextRange();
range.collapse(false);
range.select();
} else if (el.setSelectionRange) {
len = this.search.val().length;
el.setSelectionRange(len, len);
}
}
// initializes search's value with nextSearchTerm (if defined by user)
// ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
if(this.search.val() === "") {
if(this.nextSearchTerm != undefined){
this.search.val(this.nextSearchTerm);
this.search.select();
}
}
this.focusser.prop("disabled", true).val("");
this.updateResults(true);
this.opts.element.trigger($.Event("select2-open"));
},
// single
close: function () {
if (!this.opened()) return;
this.parent.close.apply(this, arguments);
this.focusser.prop("disabled", false);
if (this.opts.shouldFocusInput(this)) {
this.focusser.focus();
}
},
// single
focus: function () {
if (this.opened()) {
this.close();
} else {
this.focusser.prop("disabled", false);
if (this.opts.shouldFocusInput(this)) {
this.focusser.focus();
}
}
},
// single
isFocused: function () {
return this.container.hasClass("select2-container-active");
},
// single
cancel: function () {
this.parent.cancel.apply(this, arguments);
this.focusser.prop("disabled", false);
if (this.opts.shouldFocusInput(this)) {
this.focusser.focus();
}
},
// single
destroy: function() {
$("label[for='" + this.focusser.attr('id') + "']")
.attr('for', this.opts.element.attr("id"));
this.parent.destroy.apply(this, arguments);
},
// single
initContainer: function () {
var selection,
container = this.container,
dropdown = this.dropdown,
idSuffix = nextUid(),
elementLabel;
if (this.opts.minimumResultsForSearch < 0) {
this.showSearch(false);
} else {
this.showSearch(true);
}
this.selection = selection = container.find(".select2-choice");
this.focusser = container.find(".select2-focusser");
// add aria associations
selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
this.results.attr("id", "select2-results-"+idSuffix);
this.search.attr("aria-owns", "select2-results-"+idSuffix);
// rewrite labels from original element to focusser
this.focusser.attr("id", "s2id_autogen"+idSuffix);
elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
this.focusser.prev()
.text(elementLabel.text())
.attr('for', this.focusser.attr('id'));
// Ensure the original element retains an accessible name
var originalTitle = this.opts.element.attr("title");
this.opts.element.attr("title", (originalTitle || elementLabel.text()));
this.focusser.attr("tabindex", this.elementTabIndex);
// write label for search field using the label from the focusser element
this.search.attr("id", this.focusser.attr('id') + '_search');
this.search.prev()
.text($("label[for='" + this.focusser.attr('id') + "']").text())
.attr('for', this.search.attr('id'));
this.search.on("keydown", this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
// prevent the page from scrolling
killEvent(e);
return;
}
switch (e.which) {
case KEY.UP:
case KEY.DOWN:
this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
killEvent(e);
return;
case KEY.ENTER:
this.selectHighlighted();
killEvent(e);
return;
case KEY.TAB:
this.selectHighlighted({noFocus: true});
return;
case KEY.ESC:
this.cancel(e);
killEvent(e);
return;
}
}));
this.search.on("blur", this.bind(function(e) {
// a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
// without this the search field loses focus which is annoying
if (document.activeElement === this.body().get(0)) {
window.setTimeout(this.bind(function() {
if (this.opened()) {
this.search.focus();
}
}), 0);
}
}));
this.focusser.on("keydown", this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
return;
}
if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
killEvent(e);
return;
}
if (e.which == KEY.DOWN || e.which == KEY.UP
|| (e.which == KEY.ENTER && this.opts.openOnEnter)) {
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
this.open();
killEvent(e);
return;
}
if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
if (this.opts.allowClear) {
this.clear();
}
killEvent(e);
return;
}
}));
installKeyUpChangeEvent(this.focusser);
this.focusser.on("keyup-change input", this.bind(function(e) {
if (this.opts.minimumResultsForSearch >= 0) {
e.stopPropagation();
if (this.opened()) return;
this.open();
}
}));
selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
this.clear();
killEventImmediately(e);
this.close();
this.selection.focus();
}));
selection.on("mousedown touchstart", this.bind(function (e) {
// Prevent IE from generating a click event on the body
reinsertElement(selection);
if (!this.container.hasClass("select2-container-active")) {
this.opts.element.trigger($.Event("select2-focus"));
}
if (this.opened()) {
this.close();
} else if (this.isInterfaceEnabled()) {
this.open();
}
killEvent(e);
}));
dropdown.on("mousedown touchstart", this.bind(function() {
if (this.opts.shouldFocusInput(this)) {
this.search.focus();
}
}));
selection.on("focus", this.bind(function(e) {
killEvent(e);
}));
this.focusser.on("focus", this.bind(function(){
if (!this.container.hasClass("select2-container-active")) {
this.opts.element.trigger($.Event("select2-focus"));
}
this.container.addClass("select2-container-active");
})).on("blur", this.bind(function() {
if (!this.opened()) {
this.container.removeClass("select2-container-active");
this.opts.element.trigger($.Event("select2-blur"));
}
}));
this.search.on("focus", this.bind(function(){
if (!this.container.hasClass("select2-container-active")) {
this.opts.element.trigger($.Event("select2-focus"));
}
this.container.addClass("select2-container-active");
}));
this.initContainerWidth();
this.opts.element.addClass("select2-offscreen");
this.setPlaceholder();
},
// single
clear: function(triggerChange) {
var data=this.selection.data("select2-data");
if (data) { // guard against queued quick consecutive clicks
var evt = $.Event("select2-clearing");
this.opts.element.trigger(evt);
if (evt.isDefaultPrevented()) {
return;
}
var placeholderOption = this.getPlaceholderOption();
this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
this.selection.find(".select2-chosen").empty();
this.selection.removeData("select2-data");
this.setPlaceholder();
if (triggerChange !== false){
this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
this.triggerChange({removed:data});
}
}
},
/**
* Sets selection based on source element's value
*/
// single
initSelection: function () {
var selected;
if (this.isPlaceholderOptionSelected()) {
this.updateSelection(null);
this.close();
this.setPlaceholder();
} else {
var self = this;
this.opts.initSelection.call(null, this.opts.element, function(selected){
if (selected !== undefined && selected !== null) {
self.updateSelection(selected);
self.close();
self.setPlaceholder();
self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val());
}
});
}
},
isPlaceholderOptionSelected: function() {
var placeholderOption;
if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
|| (this.opts.element.val() === "")
|| (this.opts.element.val() === undefined)
|| (this.opts.element.val() === null);
},
// single
prepareOpts: function () {
var opts = this.parent.prepareOpts.apply(this, arguments),
self=this;
if (opts.element.get(0).tagName.toLowerCase() === "select") {
// install the selection initializer
opts.initSelection = function (element, callback) {
var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
// a single select box always has a value, no need to null check 'selected'
callback(self.optionToData(selected));
};
} else if ("data" in opts) {
// install default initSelection when applied to hidden input and data is local
opts.initSelection = opts.initSelection || function (element, callback) {
var id = element.val();
//search in data by id, storing the actual matching item
var match = null;
opts.query({
matcher: function(term, text, el){
var is_match = equal(id, opts.id(el));
if (is_match) {
match = el;
}
return is_match;
},
callback: !$.isFunction(callback) ? $.noop : function() {
callback(match);
}
});
};
}
return opts;
},
// single
getPlaceholder: function() {
// if a placeholder is specified on a single select without a valid placeholder option ignore it
if (this.select) {
if (this.getPlaceholderOption() === undefined) {
return undefined;
}
}
return this.parent.getPlaceholder.apply(this, arguments);
},
// single
setPlaceholder: function () {
var placeholder = this.getPlaceholder();
if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
// check for a placeholder option if attached to a select
if (this.select && this.getPlaceholderOption() === undefined) return;
this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
this.selection.addClass("select2-default");
this.container.removeClass("select2-allowclear");
}
},
// single
postprocessResults: function (data, initial, noHighlightUpdate) {
var selected = 0, self = this, showSearchInput = true;
// find the selected element in the result list
this.findHighlightableChoices().each2(function (i, elm) {
if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
selected = i;
return false;
}
});
// and highlight it
if (noHighlightUpdate !== false) {
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
this.highlight(0);
}
}
// hide the search box if this is the first we got the results and there are enough of them for search
if (initial === true) {
var min = this.opts.minimumResultsForSearch;
if (min >= 0) {
this.showSearch(countResults(data.results) >= min);
}
}
},
// single
showSearch: function(showSearchInput) {
if (this.showSearchInput === showSearchInput) return;
this.showSearchInput = showSearchInput;
this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
//add "select2-with-searchbox" to the container if search box is shown
$(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
},
// single
onSelect: function (data, options) {
if (!this.triggerSelect(data)) { return; }
var old = this.opts.element.val(),
oldData = this.data();
this.opts.element.val(this.id(data));
this.updateSelection(data);
this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
this.close();
if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
this.focusser.focus();
}
if (!equal(old, this.id(data))) {
this.triggerChange({ added: data, removed: oldData });
}
},
// single
updateSelection: function (data) {
var container=this.selection.find(".select2-chosen"), formatted, cssClass;
this.selection.data("select2-data", data);
container.empty();
if (data !== null) {
formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
}
if (formatted !== undefined) {
container.append(formatted);
}
cssClass=this.opts.formatSelectionCssClass(data, container);
if (cssClass !== undefined) {
container.addClass(cssClass);
}
this.selection.removeClass("select2-default");
if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
this.container.addClass("select2-allowclear");
}
},
// single
val: function () {
var val,
triggerChange = false,
data = null,
self = this,
oldData = this.data();
if (arguments.length === 0) {
return this.opts.element.val();
}
val = arguments[0];
if (arguments.length > 1) {
triggerChange = arguments[1];
}
if (this.select) {
this.select
.val(val)
.find("option").filter(function() { return this.selected }).each2(function (i, elm) {
data = self.optionToData(elm);
return false;
});
this.updateSelection(data);
this.setPlaceholder();
if (triggerChange) {
this.triggerChange({added: data, removed:oldData});
}
} else {
// val is an id. !val is true for [undefined,null,'',0] - 0 is legal
if (!val && val !== 0) {
this.clear(triggerChange);
return;
}
if (this.opts.initSelection === undefined) {
throw new Error("cannot call val() if initSelection() is not defined");
}
this.opts.element.val(val);
this.opts.initSelection(this.opts.element, function(data){
self.opts.element.val(!data ? "" : self.id(data));
self.updateSelection(data);
self.setPlaceholder();
if (triggerChange) {
self.triggerChange({added: data, removed:oldData});
}
});
}
},
// single
clearSearch: function () {
this.search.val("");
this.focusser.val("");
},
// single
data: function(value) {
var data,
triggerChange = false;
if (arguments.length === 0) {
data = this.selection.data("select2-data");
if (data == undefined) data = null;
return data;
} else {
if (arguments.length > 1) {
triggerChange = arguments[1];
}
if (!value) {
this.clear(triggerChange);
} else {
data = this.data();
this.opts.element.val(!value ? "" : this.id(value));
this.updateSelection(value);
if (triggerChange) {
this.triggerChange({added: value, removed:data});
}
}
}
}
});
MultiSelect2 = clazz(AbstractSelect2, {
// multi
createContainer: function () {
var container = $(document.createElement("div")).attr({
"class": "select2-container select2-container-multi"
}).html([
"<ul class='select2-choices'>",
" <li class='select2-search-field'>",
" <label for='' class='select2-offscreen'></label>",
" <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
" </li>",
"</ul>",
"<div class='select2-drop select2-drop-multi select2-display-none'>",
" <ul class='select2-results'>",
" </ul>",
"</div>"].join(""));
return container;
},
// multi
prepareOpts: function () {
var opts = this.parent.prepareOpts.apply(this, arguments),
self=this;
// TODO validate placeholder is a string if specified
if (opts.element.get(0).tagName.toLowerCase() === "select") {
// install the selection initializer
opts.initSelection = function (element, callback) {
var data = [];
element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
data.push(self.optionToData(elm));
});
callback(data);
};
} else if ("data" in opts) {
// install default initSelection when applied to hidden input and data is local
opts.initSelection = opts.initSelection || function (element, callback) {
var ids = splitVal(element.val(), opts.separator);
//search in data by array of ids, storing matching items in a list
var matches = [];
opts.query({
matcher: function(term, text, el){
var is_match = $.grep(ids, function(id) {
return equal(id, opts.id(el));
}).length;
if (is_match) {
matches.push(el);
}
return is_match;
},
callback: !$.isFunction(callback) ? $.noop : function() {
// reorder matches based on the order they appear in the ids array because right now
// they are in the order in which they appear in data array
var ordered = [];
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
for (var j = 0; j < matches.length; j++) {
var match = matches[j];
if (equal(id, opts.id(match))) {
ordered.push(match);
matches.splice(j, 1);
break;
}
}
}
callback(ordered);
}
});
};
}
return opts;
},
// multi
selectChoice: function (choice) {
var selected = this.container.find(".select2-search-choice-focus");
if (selected.length && choice && choice[0] == selected[0]) {
} else {
if (selected.length) {
this.opts.element.trigger("choice-deselected", selected);
}
selected.removeClass("select2-search-choice-focus");
if (choice && choice.length) {
this.close();
choice.addClass("select2-search-choice-focus");
this.opts.element.trigger("choice-selected", choice);
}
}
},
// multi
destroy: function() {
$("label[for='" + this.search.attr('id') + "']")
.attr('for', this.opts.element.attr("id"));
this.parent.destroy.apply(this, arguments);
},
// multi
initContainer: function () {
var selector = ".select2-choices", selection;
this.searchContainer = this.container.find(".select2-search-field");
this.selection = selection = this.container.find(selector);
var _this = this;
this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
//killEvent(e);
_this.search[0].focus();
_this.selectChoice($(this));
});
// rewrite labels from original element to focusser
this.search.attr("id", "s2id_autogen"+nextUid());
this.search.prev()
.text($("label[for='" + this.opts.element.attr("id") + "']").text())
.attr('for', this.search.attr('id'));
this.search.on("input paste", this.bind(function() {
if (!this.isInterfaceEnabled()) return;
if (!this.opened()) {
this.open();
}
}));
this.search.attr("tabindex", this.elementTabIndex);
this.keydowns = 0;
this.search.on("keydown", this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
++this.keydowns;
var selected = selection.find(".select2-search-choice-focus");
var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
var next = selected.next(".select2-search-choice:not(.select2-locked)");
var pos = getCursorInfo(this.search);
if (selected.length &&
(e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
var selectedChoice = selected;
if (e.which == KEY.LEFT && prev.length) {
selectedChoice = prev;
}
else if (e.which == KEY.RIGHT) {
selectedChoice = next.length ? next : null;
}
else if (e.which === KEY.BACKSPACE) {
if (this.unselect(selected.first())) {
this.search.width(10);
selectedChoice = prev.length ? prev : next;
}
} else if (e.which == KEY.DELETE) {
if (this.unselect(selected.first())) {
this.search.width(10);
selectedChoice = next.length ? next : null;
}
} else if (e.which == KEY.ENTER) {
selectedChoice = null;
}
this.selectChoice(selectedChoice);
killEvent(e);
if (!selectedChoice || !selectedChoice.length) {
this.open();
}
return;
} else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
|| e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
killEvent(e);
return;
} else {
this.selectChoice(null);
}
if (this.opened()) {
switch (e.which) {
case KEY.UP:
case KEY.DOWN:
this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
killEvent(e);
return;
case KEY.ENTER:
this.selectHighlighted();
killEvent(e);
return;
case KEY.TAB:
this.selectHighlighted({noFocus:true});
this.close();
return;
case KEY.ESC:
this.cancel(e);
killEvent(e);
return;
}
}
if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
|| e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
return;
}
if (e.which === KEY.ENTER) {
if (this.opts.openOnEnter === false) {
return;
} else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
return;
}
}
this.open();
if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
// prevent the page from scrolling
killEvent(e);
}
if (e.which === KEY.ENTER) {
// prevent form from being submitted
killEvent(e);
}
}));
this.search.on("keyup", this.bind(function (e) {
this.keydowns = 0;
this.resizeSearch();
})
);
this.search.on("blur", this.bind(function(e) {
this.container.removeClass("select2-container-active");
this.search.removeClass("select2-focused");
this.selectChoice(null);
if (!this.opened()) this.clearSearch();
e.stopImmediatePropagation();
this.opts.element.trigger($.Event("select2-blur"));
}));
this.container.on("click", selector, this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
if ($(e.target).closest(".select2-search-choice").length > 0) {
// clicked inside a select2 search choice, do not open
return;
}
this.selectChoice(null);
this.clearPlaceholder();
if (!this.container.hasClass("select2-container-active")) {
this.opts.element.trigger($.Event("select2-focus"));
}
this.open();
this.focusSearch();
e.preventDefault();
}));
this.container.on("focus", selector, this.bind(function () {
if (!this.isInterfaceEnabled()) return;
if (!this.container.hasClass("select2-container-active")) {
this.opts.element.trigger($.Event("select2-focus"));
}
this.container.addClass("select2-container-active");
this.dropdown.addClass("select2-drop-active");
this.clearPlaceholder();
}));
this.initContainerWidth();
this.opts.element.addClass("select2-offscreen");
// set the placeholder if necessary
this.clearSearch();
},
// multi
enableInterface: function() {
if (this.parent.enableInterface.apply(this, arguments)) {
this.search.prop("disabled", !this.isInterfaceEnabled());
}
},
// multi
initSelection: function () {
var data;
if (this.opts.element.val() === "" && this.opts.element.text() === "") {
this.updateSelection([]);
this.close();
// set the placeholder if necessary
this.clearSearch();
}
if (this.select || this.opts.element.val() !== "") {
var self = this;
this.opts.initSelection.call(null, this.opts.element, function(data){
if (data !== undefined && data !== null) {
self.updateSelection(data);
self.close();
// set the placeholder if necessary
self.clearSearch();
}
});
}
},
// multi
clearSearch: function () {
var placeholder = this.getPlaceholder(),
maxWidth = this.getMaxSearchWidth();
if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
this.search.val(placeholder).addClass("select2-default");
// stretch the search box to full width of the container so as much of the placeholder is visible as possible
// we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
} else {
this.search.val("").width(10);
}
},
// multi
clearPlaceholder: function () {
if (this.search.hasClass("select2-default")) {
this.search.val("").removeClass("select2-default");
}
},
// multi
opening: function () {
this.clearPlaceholder(); // should be done before super so placeholder is not used to search
this.resizeSearch();
this.parent.opening.apply(this, arguments);
this.focusSearch();
// initializes search's value with nextSearchTerm (if defined by user)
// ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
if(this.search.val() === "") {
if(this.nextSearchTerm != undefined){
this.search.val(this.nextSearchTerm);
this.search.select();
}
}
this.updateResults(true);
if (this.opts.shouldFocusInput(this)) {
this.search.focus();
}
this.opts.element.trigger($.Event("select2-open"));
},
// multi
close: function () {
if (!this.opened()) return;
this.parent.close.apply(this, arguments);
},
// multi
focus: function () {
this.close();
this.search.focus();
},
// multi
isFocused: function () {
return this.search.hasClass("select2-focused");
},
// multi
updateSelection: function (data) {
var ids = [], filtered = [], self = this;
// filter out duplicates
$(data).each(function () {
if (indexOf(self.id(this), ids) < 0) {
ids.push(self.id(this));
filtered.push(this);
}
});
data = filtered;
this.selection.find(".select2-search-choice").remove();
$(data).each(function () {
self.addSelectedChoice(this);
});
self.postprocessResults();
},
// multi
tokenize: function() {
var input = this.search.val();
input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
if (input != null && input != undefined) {
this.search.val(input);
if (input.length > 0) {
this.open();
}
}
},
// multi
onSelect: function (data, options) {
if (!this.triggerSelect(data)) { return; }
this.addSelectedChoice(data);
this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
// keep track of the search's value before it gets cleared
this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
this.clearSearch();
this.updateResults();
if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
if (this.opts.closeOnSelect) {
this.close();
this.search.width(10);
} else {
if (this.countSelectableResults()>0) {
this.search.width(10);
this.resizeSearch();
if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
// if we reached max selection size repaint the results so choices
// are replaced with the max selection reached message
this.updateResults(true);
} else {
// initializes search's value with nextSearchTerm and update search result
if(this.nextSearchTerm != undefined){
this.search.val(this.nextSearchTerm);
this.updateResults();
this.search.select();
}
}
this.positionDropdown();
} else {
// if nothing left to select close
this.close();
this.search.width(10);
}
}
// since its not possible to select an element that has already been
// added we do not need to check if this is a new element before firing change
this.triggerChange({ added: data });
if (!options || !options.noFocus)
this.focusSearch();
},
// multi
cancel: function () {
this.close();
this.focusSearch();
},
addSelectedChoice: function (data) {
var enableChoice = !data.locked,
enabledItem = $(
"<li class='select2-search-choice'>" +
" <div></div>" +
" <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
"</li>"),
disabledItem = $(
"<li class='select2-search-choice select2-locked'>" +
"<div></div>" +
"</li>");
var choice = enableChoice ? enabledItem : disabledItem,
id = this.id(data),
val = this.getVal(),
formatted,
cssClass;
formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
if (formatted != undefined) {
choice.find("div").replaceWith("<div>"+formatted+"</div>");
}
cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
if (cssClass != undefined) {
choice.addClass(cssClass);
}
if(enableChoice){
choice.find(".select2-search-choice-close")
.on("mousedown", killEvent)
.on("click dblclick", this.bind(function (e) {
if (!this.isInterfaceEnabled()) return;
this.unselect($(e.target));
this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
killEvent(e);
this.close();
this.focusSearch();
})).on("focus", this.bind(function () {
if (!this.isInterfaceEnabled()) return;
this.container.addClass("select2-container-active");
this.dropdown.addClass("select2-drop-active");
}));
}
choice.data("select2-data", data);
choice.insertBefore(this.searchContainer);
val.push(id);
this.setVal(val);
},
// multi
unselect: function (selected) {
var val = this.getVal(),
data,
index;
selected = selected.closest(".select2-search-choice");
if (selected.length === 0) {
throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
}
data = selected.data("select2-data");
if (!data) {
// prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
// and invoked on an element already removed
return;
}
var evt = $.Event("select2-removing");
evt.val = this.id(data);
evt.choice = data;
this.opts.element.trigger(evt);
if (evt.isDefaultPrevented()) {
return false;
}
while((index = indexOf(this.id(data), val)) >= 0) {
val.splice(index, 1);
this.setVal(val);
if (this.select) this.postprocessResults();
}
selected.remove();
this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
this.triggerChange({ removed: data });
return true;
},
// multi
postprocessResults: function (data, initial, noHighlightUpdate) {
var val = this.getVal(),
choices = this.results.find(".select2-result"),
compound = this.results.find(".select2-result-with-children"),
self = this;
choices.each2(function (i, choice) {
var id = self.id(choice.data("select2-data"));
if (indexOf(id, val) >= 0) {
choice.addClass("select2-selected");
// mark all children of the selected parent as selected
choice.find(".select2-result-selectable").addClass("select2-selected");
}
});
compound.each2(function(i, choice) {
// hide an optgroup if it doesn't have any selectable children
if (!choice.is('.select2-result-selectable')
&& choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
choice.addClass("select2-selected");
}
});
if (this.highlight() == -1 && noHighlightUpdate !== false){
self.highlight(0);
}
//If all results are chosen render formatNoMatches
if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.search.val()) + "</li>");
}
}
}
},
// multi
getMaxSearchWidth: function() {
return this.selection.width() - getSideBorderPadding(this.search);
},
// multi
resizeSearch: function () {
var minimumWidth, left, maxWidth, containerLeft, searchWidth,
sideBorderPadding = getSideBorderPadding(this.search);
minimumWidth = measureTextWidth(this.search) + 10;
left = this.search.offset().left;
maxWidth = this.selection.width();
containerLeft = this.selection.offset().left;
searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
if (searchWidth < minimumWidth) {
searchWidth = maxWidth - sideBorderPadding;
}
if (searchWidth < 40) {
searchWidth = maxWidth - sideBorderPadding;
}
if (searchWidth <= 0) {
searchWidth = minimumWidth;
}
this.search.width(Math.floor(searchWidth));
},
// multi
getVal: function () {
var val;
if (this.select) {
val = this.select.val();
return val === null ? [] : val;
} else {
val = this.opts.element.val();
return splitVal(val, this.opts.separator);
}
},
// multi
setVal: function (val) {
var unique;
if (this.select) {
this.select.val(val);
} else {
unique = [];
// filter out duplicates
$(val).each(function () {
if (indexOf(this, unique) < 0) unique.push(this);
});
this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
}
},
// multi
buildChangeDetails: function (old, current) {
var current = current.slice(0),
old = old.slice(0);
// remove intersection from each array
for (var i = 0; i < current.length; i++) {
for (var j = 0; j < old.length; j++) {
if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
current.splice(i, 1);
if(i>0){
i--;
}
old.splice(j, 1);
j--;
}
}
}
return {added: current, removed: old};
},
// multi
val: function (val, triggerChange) {
var oldData, self=this;
if (arguments.length === 0) {
return this.getVal();
}
oldData=this.data();
if (!oldData.length) oldData=[];
// val is an id. !val is true for [undefined,null,'',0] - 0 is legal
if (!val && val !== 0) {
this.opts.element.val("");
this.updateSelection([]);
this.clearSearch();
if (triggerChange) {
this.triggerChange({added: this.data(), removed: oldData});
}
return;
}
// val is a list of ids
this.setVal(val);
if (this.select) {
this.opts.initSelection(this.select, this.bind(this.updateSelection));
if (triggerChange) {
this.triggerChange(this.buildChangeDetails(oldData, this.data()));
}
} else {
if (this.opts.initSelection === undefined) {
throw new Error("val() cannot be called if initSelection() is not defined");
}
this.opts.initSelection(this.opts.element, function(data){
var ids=$.map(data, self.id);
self.setVal(ids);
self.updateSelection(data);
self.clearSearch();
if (triggerChange) {
self.triggerChange(self.buildChangeDetails(oldData, self.data()));
}
});
}
this.clearSearch();
},
// multi
onSortStart: function() {
if (this.select) {
throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
}
// collapse search field into 0 width so its container can be collapsed as well
this.search.width(0);
// hide the container
this.searchContainer.hide();
},
// multi
onSortEnd:function() {
var val=[], self=this;
// show search and move it to the end of the list
this.searchContainer.show();
// make sure the search container is the last item in the list
this.searchContainer.appendTo(this.searchContainer.parent());
// since we collapsed the width in dragStarted, we resize it here
this.resizeSearch();
// update selection
this.selection.find(".select2-search-choice").each(function() {
val.push(self.opts.id($(this).data("select2-data")));
});
this.setVal(val);
this.triggerChange();
},
// multi
data: function(values, triggerChange) {
var self=this, ids, old;
if (arguments.length === 0) {
return this.selection
.children(".select2-search-choice")
.map(function() { return $(this).data("select2-data"); })
.get();
} else {
old = this.data();
if (!values) { values = []; }
ids = $.map(values, function(e) { return self.opts.id(e); });
this.setVal(ids);
this.updateSelection(values);
this.clearSearch();
if (triggerChange) {
this.triggerChange(this.buildChangeDetails(old, this.data()));
}
}
}
});
$.fn.select2 = function () {
var args = Array.prototype.slice.call(arguments, 0),
opts,
select2,
method, value, multiple,
allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
valueMethods = ["opened", "isFocused", "container", "dropdown"],
propertyMethods = ["val", "data"],
methodsMap = { search: "externalSearch" };
this.each(function () {
if (args.length === 0 || typeof(args[0]) === "object") {
opts = args.length === 0 ? {} : $.extend({}, args[0]);
opts.element = $(this);
if (opts.element.get(0).tagName.toLowerCase() === "select") {
multiple = opts.element.prop("multiple");
} else {
multiple = opts.multiple || false;
if ("tags" in opts) {opts.multiple = multiple = true;}
}
select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
select2.init(opts);
} else if (typeof(args[0]) === "string") {
if (indexOf(args[0], allowedMethods) < 0) {
throw "Unknown method: " + args[0];
}
value = undefined;
select2 = $(this).data("select2");
if (select2 === undefined) return;
method=args[0];
if (method === "container") {
value = select2.container;
} else if (method === "dropdown") {
value = select2.dropdown;
} else {
if (methodsMap[method]) method = methodsMap[method];
value = select2[method].apply(select2, args.slice(1));
}
if (indexOf(args[0], valueMethods) >= 0
|| (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
return false; // abort the iteration, ready to return first matched value
}
} else {
throw "Invalid arguments to select2 plugin: " + args;
}
});
return (value === undefined) ? this : value;
};
// plugin defaults, accessible to users
$.fn.select2.defaults = {
width: "copy",
loadMorePadding: 0,
closeOnSelect: true,
openOnEnter: true,
containerCss: {},
dropdownCss: {},
containerCssClass: "",
dropdownCssClass: "",
formatResult: function(result, container, query, escapeMarkup) {
var markup=[];
markMatch(result.text, query.term, markup, escapeMarkup);
return markup.join("");
},
formatSelection: function (data, container, escapeMarkup) {
return data ? escapeMarkup(data.text) : undefined;
},
sortResults: function (results, container, query) {
return results;
},
formatResultCssClass: function(data) {return data.css;},
formatSelectionCssClass: function(data, container) {return undefined;},
formatMatches: function (matches) { return matches + " results are available, use up and down arrow keys to navigate."; },
formatNoMatches: function () { return "No matches found"; },
formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1? "" : "s"); },
formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
formatLoadMore: function (pageNumber) { return "Loading more results…"; },
formatSearching: function () { return "Searching…"; },
minimumResultsForSearch: 0,
minimumInputLength: 0,
maximumInputLength: null,
maximumSelectionSize: 0,
id: function (e) { return e == undefined ? null : e.id; },
matcher: function(term, text) {
return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
},
separator: ",",
tokenSeparators: [],
tokenizer: defaultTokenizer,
escapeMarkup: defaultEscapeMarkup,
blurOnChange: false,
selectOnBlur: false,
adaptContainerCssClass: function(c) { return c; },
adaptDropdownCssClass: function(c) { return null; },
nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
searchInputPlaceholder: '',
createSearchChoicePosition: 'top',
shouldFocusInput: function (instance) {
// Never focus the input if search is disabled
if (instance.opts.minimumResultsForSearch < 0) {
return false;
}
return true;
}
};
$.fn.select2.ajaxDefaults = {
transport: $.ajax,
params: {
type: "GET",
cache: false,
dataType: "json"
}
};
// exports
window.Select2 = {
query: {
ajax: ajax,
local: local,
tags: tags
}, util: {
debounce: debounce,
markMatch: markMatch,
escapeMarkup: defaultEscapeMarkup,
stripDiacritics: stripDiacritics
}, "class": {
"abstract": AbstractSelect2,
"single": SingleSelect2,
"multi": MultiSelect2
}
};
}(jQuery));
// Angular Rails Templates 0.1.1
//
// angular_templates.ignore_prefix: templates/
// angular_templates.markups: ["erb", "haml", "slim", "str"]
// angular_templates.htmlcompressor: false
angular.module("templates", []);
// Angular Rails Template
// source: app/assets/templates/hubs/_new_hub_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("hubs/_new_hub_modal.html", "<div class='modal' id='hubModal' ng-controller='HubsCtrl'>\n <div class='modal-header'>\n <div class='close pull-right' data-dismiss='modal'>&times;</div>\n <h3>Create a new Group</h3>\n <div alert-bar alertmessageclear='alertService.alertClass' class='content'></div>\n </div>\n <div class='modal-body'>\n <form name='newHubForm'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <input class='input-xlarge' id='hub_group_name' ng-minlength='5' ng-model='hub_attributes.group_name' placeholder='Enter your new group name' required type='text'>\n </div>\n <div class='controls'>\n <input class='input-xlarge' id='hub_formatted_location' name='formatted_location' ng-model='hub_attributes.formatted_location' placeholder=\"Enter the Group's Location\" required sv-google-place type='text'>\n <span ng-show='!newHubForm.formatted_location.$valid'>Choose a valid location.</span>\n </div>\n </div>\n </fieldset>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='addHub()' ng-disabled='!newHubForm.$valid'>Add Group <i class=\"glyphicon glyphicon-ok\"></i></button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/pages/about.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/about.html", "<div class='static_page'>\n <br>\n <div class='pull-right'>\n <img src='/assets/search_example.png' width='400'>\n </div>\n <h2>Consensus Simplified</h2>\n <div class='content_offset'>\n <ol class='attention_red'>\n <li>Find a group</li>\n <li>See what they're proposing</li>\n <li>Help the best ideas reach consensus</li>\n </ol>\n </div>\n <div class='content_offset'>\n <h3>\n Start by finding your group in the top box.\n </h3>\n </div>\n <h2>How it Works</h2>\n <div class='content_div'>\n First we capture your ideas, called Proposals. Then you get to organically improve the semantics of those proposals on your way toward reaching a majority opinion together. As the wording improves, the best proposals gain momentum by receiving support from multiple users and forming a clarity of consensus.\n </div>\n <h2>Why it Matters</h2>\n <div class='content_div'></div>\n Spokenvote is a tool to help groups of any size, from a local school board to an entire nation’s people, reach consensus with radical efficiency. We think the process should be intuitive, fun, and yes, democratic.\n <div class='content_div_half'></div>\n Deeply inspired by Wikipedia, Spokenvote is an open source non-profit intended for small groups today, but over time to reach national politics. It starts with spontaneously capturing ideas as they occur to us. When we do that as a diverse group, a\n <em>“power of the crowd”</em>\n effect is introduced. The result is often a revelation of common sense that is difficult to discover from a top-down approach.\n <h2>Contribute</h2>\n <div class='content_div'>\n Spokenvote is an open source application, and we welcome your participation in building it.\n <a ng-href='http://spokenvote.github.com/spokenvote'>Learn more.</a>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/pages/dev-forum.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/dev-forum.html", '<section id="forum_page"><div class="page_title"><div class="call_to_action">Developer Discussion Forum</div></div><div class="forum_area"><iframe frameborder="0" height="700" id="forum_embed" scrolling="no" src="javascript:void(0)" width="100%"></iframe><script type="text/javascript">document.getElementById(\'forum_embed\').src = \'https://groups.google.com/forum/embed/?place=forum/spokenvote-dev\'\n + \'&showsearch=true&showpopout=true&showtabs=true&hideforumtitle=true\'\n + \'&parenturl=\' + encodeURIComponent(window.location.href);</script></div></section>')
}]);
// Angular Rails Template
// source: app/assets/templates/pages/help.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/help.html", "<div class='content_page'>\n <h1>Using SpokenVote</h1>\n <h2>Finding What Matters to You</h2>\n <div class='content_div'>This is help for search and browse.</div>\n <h2>Adding Your Voice</h2>\n <div class='content_div'>This is help for supporting, improving and creating Proposals</div>\n <h2>Participating in SpokenVote</h2>\n <div class='content_div'>This is help for creating an account and signing in</div>\n <h2>More Resources</h2>\n <div class='content_div'>\n Please post questions or issues that aren't answered here to\n <a ng-href='https://groups.google.com/d/forum/spokenvote-dev'>our Google Group.</a>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/pages/landing.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/landing.html", '<section itemscope="" itemtype="http://schema.org/Organization"><div class="page_title"><div class="sv-logo" href="/landing"><img itemprop="logo" src="/assets/spokenvote_logomeg.png" /><span content="Spokenvote" itemprop="name"></span><span class="spoken">Spoken</span><span class="vote">Vote</span></div><div class="start"><button bs-tooltip="tooltips.newTopic" class="btn btn-getStarted btn-bold btn-large main" data-placement="bottom" ng-click="getStarted()">Start</button></div><div class="call_to_action">Your Group Decisions.</div><div class="instructions"><a href="/proposals?filter=active"><i>Browse proposals</i>,</a><span>support, create your own.</span></div><div class="boxes-123 row"><div class="col-xs-4 text-center"><i class="fa fa-pencil-square-o fa-4x"></i><h2>Prop</h2><p>Find your group to see what they\'re deciding.</p></div><div class="col-xs-4 text-center"><i class="fa fa-thumbs-o-up fa-4x"></i><h2>Buzz</h2><p>Support a proposal to buzz up popularity.</p></div><div class="col-xs-4 text-center"><i class="fa fa-code-fork fa-4x"></i><h2>Fork</h2><p>Or create a better version by forking any proposal.</p></div></div></div><div class="below_fold"><div class="fold_row row"><div class="instructions col-sm-6"><h2>How it Works</h2></div></div><div class="fold_row row"><div class="instructions col-sm-6"><h4 class="attention_red">1. Find out who is deciding</h4><h4>Search for any group making a decision. Click on a topic to see its proposals.</h4></div><div class="screen-shot-examples col-sm-6"><img class="hidden-xs" src="/assets/search_example.png" /><img class="visible-xs hidden-sm" src="/assets/search_example_xs.png" /><br /></div></div><div class="fold_row row"><div class="instructions col-sm-6"><h4 class="attention_red">2. See what they\'re proposing</h4><h4>From here you may either support a proposal or fork off your own <i>improved</i> version of it. If you don’t see the topic you are looking for, click <i>Start New Topic</i>.</h4></div><div class="screen-shot-examples col-sm-6"><img src="/assets/new_topic_example.png" /><br /></div></div><div class="fold_row row"><div class="instructions col-sm-6"><h4 class="attention_red">3. Help the best ideas reach consensus</h4><h4>As users repeatedly fork off proposal improvements, see new ideas and move their support, the best proposal is <i>buzzed</i> to the top.</h4></div><div class="screen-shot-examples col-sm-6"><img src="/assets/support_improve_example.png" /><br /></div></div><div class="fold_row row"><div class="instructions col-sm-6"><h4>As each participant adds input, the group collectively moves toward reaching concensus.</h4></div></div><div class="fold_row row"><div class="screen-shot-examples col-sm-6"><img src="/assets/akropolis.jpg" /></div><div class="instructions col-sm-6"><h2>Why it Matters</h2><h4>Spokenvote is a tool to help groups of any size, from a local school board to an entire nation’s people, reach consensus with radical efficiency. We think the process should be intuitive, fun, and yes, democratic.</h4><h4>Deeply inspired by Wikipedia, Spokenvote is an open source non-profit intended for small groups today, but over time to reach national politics. It starts with spontaneously capturing ideas as they occur to us. When we do that as a diverse group, a <i> “power of the crowd” </i> effect is introduced. The result is often a revelation of common sense that is difficult to discover from a top-down approach.</h4></div></div><div class="fold_row row"><div class="screen-shot-examples col-sm-6"><img src="/assets/blacktocat.png" /></div><div class="instructions col-sm-6"><h2>Contribute</h2><h4>Spokenvote is an open source application, and we welcome your participation in building it.</h4><a ng-href="http://spokenvote.github.com/spokenvote"> Learn more.</a></div></div></div></section>')
}]);
// Angular Rails Template
// source: app/assets/templates/pages/privacy.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/privacy.html", "<div class='static_page'>\n <h2>Privacy Policy</h2>\n <h3>Public Nature of Content Submitted to Sites</h3>\n <div class='content_div'>\n The nature of posting content is to reach a public audience. Please be aware that any information that you submit to us whether through forms or email, including personally identifiable information, may be publicly displayed on sites hosted at www.spokenvote.org, or on websites not within our control. If you don’t want others to see such information, don’t submit it.\n </div>\n <h3>Content</h3>\n <div class='content_div'>\n To create a new user account, you are required to submit an email address and other personal information. We may publicly display that personal information. Your address may also be used to send you email bulletins from individual Spokenvote postings to which you subscribe, or to attribute comments or posts to you.\n </div>\n <div class='content_div'>\n If you contribute content, such as discussion comments, to any of our postings, your contribution may be publicly displayed including personally identifiable information.\n </div>\n <div class='content_div'>\n The default setting for individual postings allows outside search engines, indexes and third party sites to scan all postings, create hyperlinks to individual postings or distribute it further via feeds. At this time these settings may not be changed.\n </div>\n <div class='content_div'>\n Each time you update your postings, it may also send pings or feeds to sites that index or track newly updated postings. These sites may publicly display a hyperlink to your site and the time that you updated it. If your site publishes XML feeds, content from or links to your site may be publicly displayed on third party sites.\n </div>\n <div class='content_div'>\n Depending on the settings on your account, email bulletins that contain content from your site may be sent out to subscribers.\n </div>\n <h3>IP Addresses and Referrers</h3>\n <div class='content_div'>\n We may publicly display the IP addresses of visitors and contributors to individual postings hosted at www.spokenvote.org.\n </div>\n <div class='content_div'>\n We may use your IP address to help diagnose problems with our server, to tailor site content and to format the site and software to user needs, and to generate aggregate statistical reports. We may use aggregate visitor data to prepare publicly displayed reports regarding the traffic on individual postings, site popularity rankings, and referrers that visitors use to access individual postings.\n </div>\n <h3>Cookies</h3>\n <div class='content_div'>\n If you visit our web site, we may use session cookies while your browser is open, or while you are logged into our site. To facilitate our registration and login functions, we may use cookies to recognize you when you return to our website or to individual postings hosted our site. These cookies allow us to keep track of your username and password, so that you do not have to resubmit the information to log into your account. Always remember to log out of your site or account so that the computer’s other users cannot access your account.\n </div>\n <h3>Disclosure of Information to Spokenvote administration and to Law Enforcement</h3>\n <div class='content_div'>\n Available log records, and all data stored on our servers may be accessed by our system administrators. The system administrators may produce these records and data upon request or suspected violation of our terms of use. If we receive a warrant or subpoena for user information, we may disclose requested records to law enforcement authorities or outside parties seeking information through the legal process.\n </div>\n <h3>Links to Independent Websites</h3>\n <div class='content_div'>\n Postings hosted by the Weblogs at Spokenvote may link to independently run web sites outside of the www.spokenvote.org domain. Spokenvote is not responsible for the privacy practices or content of such web sites.\n </div>\n <h3>Children’s Privacy</h3>\n <div class='content_div'>\n The content at Spokenvote is intended for adults and we will not knowingly collect personal information from children under 13 years old. If you are a parent or legal guardian of a child under age 13 who has become a member of a site hosted by www.spokenvote.org, please contact our administrator to notify us of a suspected violation.\n </div>\n <h3>Contacting Us</h3>\n <div class='content_div'>\n If you have any questions about this privacy statement or the general terms of use of the site, you may contact us by posting to the Spokenvote group on www.spokenvote.org\n </div>\n <div class='content_div'>\n Be Sociable, Share!\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/pages/terms-of-use.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/terms-of-use.html", "<div class='static_page'>\n <h2>Terms of Use</h2>\n <h3>Welcome to Spokenvote!</h3>\n <div class='content_div'>\n We don’t want to inundate you with legalese, but we need to make clear our respective rights and responsibilities related to this service. So, Spokenvote offers this tool (the “Services”) to you subject to the terms and conditions of use (“Terms”) contained herein. By accessing, creating or contributing to any blogs hosted at Spokenvote.org, and in consideration for the Services we provide to you, you agree to abide by these Terms. Please read them carefully before posting or creating any content.\n </div>\n <h3>1. Rights in the Content You Submit</h3>\n <div class='content_div'>\n Default Creative Commons Public License\n </div>\n <div class='content_div'>\n Unless you specify otherwise, any and all works of authorship copyrightable by you (“Content”) are submitted under the terms of an Attribution-ShareAlike Creative Commons Public License. Under this license, you permit anyone to copy, distribute, display and perform your Content, royalty-free, on the condition that they credit your authorship each time they do so. You also permit others to distribute derivative works of your Content, but only if they do so under the same Attribution-ShareAlike license that governs your original Content.\n </div>\n <div class='content_div'>\n Please read the full text of the Attribution-ShareAlike Creative Commons Public License.\n Amongst other things, this license permits RSS aggregators to copy, distribute, display and perform any Content using RSS.\n </div>\n <div class='content_div'>\n By posting your Content using the Services, you are granting Spokenvote a non-exclusive, royalty-free, perpetual, and worldwide license to use your Content in connection with the operation of the Services, including, without limitation, the license rights to copy, distribute, transmit, publicly display, publicly perform, reproduce, edit, translate and reformat your Content, and/or to incorporate it into a collective work.\n </div>\n <div class='content_div'>\n Attribution\n </div>\n <div class='content_div'>\n When publicly displaying, publicly performing, reproducing or distributing copies of your Content, or Content as incorporated into a collective work, Spokenvote will make best efforts to credit your authorship. You grant Spokenvote permission to use your name for such attribution purposes. You, likewise, agree to represent yourself accurately. You acknowledge that misrepresentation may lead us, in our sole discretion, to cancel your use of the Services and delete any of your Content.\n </div>\n <h3>2. Conduct</h3>\n <div class='content_div'>\n Posting\n </div>\n <div class='content_div'>\n The founders of Spokenvote believe deeply in free speech. Given our role in offering this service and our presence together as part of the extended community, however, we must reserve the right to remove certain content that you may post. As a general matter, you may post content freely and to others, so long as the content is not illegal, obscene, defamatory, threatening, infringing of intellectual property rights, invasive of privacy or otherwise injurious or objectionable.\n </div>\n <div class='content_div'>\n You may not use the Spokenvote name to endorse or promote any product, opinion, cause or political candidate. Representation of your personal opinions as endorsed by Spokenvote is strictly prohibited.\n </div>\n <div class='content_div'>\n By posting content, you warrant and represent that you either own or otherwise control all of the rights to that content, including, without limitation, all the rights necessary for you to provide, post, upload, input or submit the content, or that your use of the content is a protected fair use. You agree that you will not knowingly and with intent to defraud provide material and misleading false information. You represent and warrant also that the content you supply does not violate these Terms, and that you will indemnify and hold Spokenvote harmless for any and all claims resulting from content you supply.\n </div>\n <div class='content_div'>\n You acknowledge that Spokenvote does not pre-screen or regularly review posted content, but that it shall have the right to remove in its sole discretion any content that it considers to violate these Terms.\n </div>\n <div class='content_div'>\n Accessing\n </div>\n <div class='content_div'>\n You understand that all content posted to http://www.Spokenvote.org/ is the sole responsibility of the individual who originally posted the content. You understand, also, that all opinions expressed by users of this site are expressed strictly in their individual capacities, and not as representatives of Spokenvote.\n </div>\n <div class='content_div'>\n You agree that Spokenvote will not be liable, under any circumstances and in any way, for any errors or omissions, loss or damage of any kind incurred as a result of use of any content posted on this site. You agree that you must evaluate and bear all risks associated with the use of any content, including any reliance on the accuracy, completeness, or usefulness of such content.\n </div>\n <div class='content_div'>\n Children\n </div>\n <div class='content_div'>\n Collecting personal information from children under the age of 13 is prohibited. No Content should be directed toward such children without the express written permission of Spokenvote.\n </div>\n <h3>3. Disclaimer of Warranties and Limitation of Liability</h3>\n <div class='content_div'>\n This site is provided on an “as is” and “as available” basis. Spokenvote makes no representations or warranties of any kind, express or implied, as to the site’s operation or the information, content or materials included on this site. To the full extent permissible by applicable law, Spokenvote hereby disclaims all warranties, express or implied, including but not limited to implied warranties of merchantability and fitness for any particular purpose. Spokenvote will not be liable for any damages of any kind arising from the use of or inability to use this site. You expressly agree that you use this site solely at your own risk.\n </div>\n <h3>4. Privacy Policy</h3>\n <div class='content_div'>\n Please be sure to read our\n <a ng-href='/privacy'>Privacy Policy,</a>\n which is incorporated herein by reference.\n </div>\n <h3>5. Modification of These Terms of Use</h3>\n <div class='content_div'>\n Spokenvote reserves the right to change, at any time, at our sole discretion, the Terms under which these Services are offered. You are responsible for regularly reviewing these Terms. Your continued use of the Services constitutes your agreement to all such Terms.\n </div>\n <h3>6. Copyright Complaints</h3>\n <div class='content_div'>\n Spokenvote respects the intellectual property of others, and requires that our users do the same. If you believe that your work has been copied and is accessible on this site in a way that constitutes copyright infringement, or that your intellectual property rights have been otherwise violated, please report to us any copyright infringements.\n </div>\n <div class='content_div'>\n By using the site you acknowledge that you have read and are bound by this agreement, as well as any other Spokenvote network usage agreements that may govern your conduct. Thank you for participating in the Weblogs At Spokenvote Law initiative. Please do not hesitate to contact us if you have questions.\n </div>\n <h3>7. Terms License</h3>\n <div class='content_div'>\n This terms of use document is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/pages/user-forum.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("pages/user-forum.html", '<section id="forum_page"><div class="page_title"><div class="call_to_action">User Discussion Forum</div></div><div class="forum_area"><iframe frameborder="0" height="700" id="forum_embed" scrolling="no" src="javascript:void(0)" width="100%"></iframe><script type="text/javascript">document.getElementById(\'forum_embed\').src = \'https://groups.google.com/forum/embed/?place=forum/spokenvote-user\'\n + \'&showsearch=true&showpopout=true&showtabs=true&hideforumtitle=true\'\n + \'&parenturl=\' + encodeURIComponent(window.location.href);</script></div></section>')
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_delete_proposal_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_delete_proposal_modal.html", "<div id='deleteProposalModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2 class='modal-title'>Delete Proposal</h2>\n <div class='selectedHub'>\n <span>\n <div class='groupName'>\n {{ clicked_proposal.hub.group_name }}\n </div>\n <div class='groupLocation'>\n {{ clicked_proposal.hub.formatted_location }}\n </div>\n </span>\n </div>\n </div>\n <div class='modal-body'>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>Are you sure you want to delete this proposal? Please note that deleting a proposal is permanent and cannot be undone.</h5>\n <div class='proposal_statement'>\n <div class='form-group'>\n <div class='originalProposal'>\n <h4>{{ clicked_proposal.statement }}</h4>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-bold' ng-click='$close()'>Cancel</button>\n <button class='btn btn-danger btn-bold' ng-click='deleteProposal()'>Delete my proposal</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_edit_proposal_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_edit_proposal_modal.html", "<div id='editProposalModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2>Edit Proposal</h2>\n <div class='selectedHub'>\n <span>\n <div class='groupName'>\n {{ clicked_proposal.hub.group_name }}\n </div>\n <div class='groupLocation'>\n {{ clicked_proposal.hub.formatted_location }}\n </div>\n </span>\n </div>\n </div>\n <div class='modal-body'>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n <form name='editProposalForm' ng-submit='saveEdit()'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>You are editing your Proposal.</h5>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <textarea class='full-width' id='proposal_statement' name='proposal_statement' ng-maxLength='140' ng-minLength='15' ng-model='editProposal.proposal.statement' placeholder='Write your main proposal statement here.' required></textarea>\n <div class='length_counter' ng-show='editProposalForm.proposal_statement.$valid'>{{ 140 - editProposal.proposal.statement.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='editProposalForm.proposal_statement.$invalid &amp;&amp; !editProposalForm.proposal_statement.$error.minlength &amp;&amp; !editProposalForm.proposal_statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='editProposalForm.proposal_statement.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='editProposalForm.proposal_statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <textarea class='full-width' id='vote_comment' name='vote_comment' ng-maxLength='280' ng-minLength='7' ng-model='editProposal.proposal.votes_attributes.comment' placeholder='Explain why others should support your proposal here.' required></textarea>\n <div class='length_counter' ng-show='editProposalForm.vote_comment.$valid'>{{ 280 - editProposal.proposal.votes_attributes.comment.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='editProposalForm.vote_comment.$invalid &amp;&amp; !editProposalForm.vote_comment.$error.minlength &amp;&amp; !editProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='editProposalForm.vote_comment.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='editProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='saveEdit()' ng-disabled='!editProposalForm.$valid'>Save your proposal changes</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_improve_proposal_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_improve_proposal_modal.html", "<div id='improveProposalModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2 class='modal-title'>Improve Proposal</h2>\n <div class='selectedHub'>\n <span>\n <div class='groupName'>\n {{ clicked_proposal.hub.group_name }}\n </div>\n <div class='groupLocation'>\n {{ clicked_proposal.hub.formatted_location }}\n </div>\n </span>\n </div>\n </div>\n <div class='modal-body'>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n <form name='improvedProposalForm'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>You are improving upon the proposal below.</h5>\n </div>\n <h5 class='hint-text'>\n <div class='fa fa-lightbulb-o'></div>\n <span>Hint! Try to make as few changes to the original proposal as possible while making your improvement.</span>\n </h5>\n <div class='proposal_statement'>\n <div class='form-group'>\n <h5>Original Proposal</h5>\n <div class='originalProposal'>\n <h4>{{ clicked_proposal.statement }}</h4>\n </div>\n </div>\n <div class='text_input'>\n <div class='form-group'>\n <h5>Your Improved Proposal</h5>\n <textarea class='full-width' id='proposal_statement' name='proposal_statement' ng-maxLength='140' ng-minLength='15' ng-model='improvedProposal.statement' placeholder='Write your improved proposal here, making as few changes to the original proposal as possible to effect your change.' required></textarea>\n <div class='length_counter happy-text' ng-show='improvedProposalForm.proposal_statement.$valid'>{{ 140 - improvedProposal.statement.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.proposal_statement.$invalid &amp;&amp; !improvedProposalForm.proposal_statement.$error.minlength &amp;&amp; !improvedProposalForm.proposal_statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.proposal_statement.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.proposal_statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n <div class='form-group'>\n <textarea class='full-width' id='vote_comment' name='vote_comment' ng-maxLength='280' ng-minLength='7' ng-model='improvedProposal.comment' placeholder='Explain why others should support your improved proposal.' required></textarea>\n <div class='length_counter happy-text' ng-show='improvedProposalForm.vote_comment.$valid'>{{ 280 - improvedProposal.comment.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.vote_comment.$invalid &amp;&amp; !improvedProposalForm.vote_comment.$error.minlength &amp;&amp; !improvedProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.vote_comment.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='improvedProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='saveImprovement()' ng-disabled='!improvedProposalForm.$valid'>Save Improved proposal</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_new_proposal_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_new_proposal_modal.html", "<div id='newProposalModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2 class='modal-title'>Start New Topic</h2>\n <ng-include src=\"'shared/_selected_hub.html'\"></ng-include>\n </div>\n <div class='modal-body'>\n <ng-include src=\"'shared/_alert_container.html'\"></ng-include>\n <form name='newProposalForm' ng-submit='votingService.saveNewProposal(modalInstance)'>\n <div class='newProposal'>\n <ng-include src=\"'shared/_change_hub.html'\"></ng-include>\n <ng-include src=\"'shared/_make_hub.html'\"></ng-include>\n <ng-include src=\"'shared/_proposal_area.html'\"></ng-include>\n </div>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='control-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='votingService.saveNewProposal(modalInstance)' ng-disabled='!newProposalForm.$valid' ng-show='sessionSettings.actions.changeHub == false'>Save your New proposal</button>\n <button class='btn btn-primary btn-bold' ng-click='sessionSettings.actions.changeHub = false' ng-disabled='!newProposalForm.formatted_location.$valid || !newProposalForm.group_name.$valid' ng-show='sessionSettings.actions.changeHub == \"new\"'>Continue</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_show.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_show.html", "<div class='proposal_container' ui-if='proposal.has_support'>\n <div class='main-proposal' id='prop{{ proposal.id }}'>\n <div class='stats' id='big-percent'>\n <div id='big-percent'>{{ proposal.votes_percentage }}</div>\n <ng-include class='pull-right' src=\"'shared/_social_buttons.html'\"></ng-include>\n </div>\n <div class='proposal_statement'>\n <h3>\n <a ng-click='showProposal(proposal)' ng-href='#'>{{ proposal.statement }}</a>\n </h3>\n </div>\n <div class='improve_support_buttons'>\n <div class='edit btn-group' ng-show='currentUser &amp;&amp; proposal.is_editable &amp;&amp; proposal.user_id == currentUser.id'>\n <span class='hidden-xs hidden-sm'>\n <button class='btn btn-primary edit' ng-click='edit(proposal)' tooltip-html-unsafe='{{tooltips.edit}}' tooltip-placement='right'>\n <i class='fa fa-pencil fa-2x'></i>\n </button>\n </span>\n <span class='hidden-md hidden-lg'>\n <button class='btn btn-primary edit' ng-click='edit(proposal)'>\n <i class='fa fa-pencil fa-2x'></i>\n </button>\n </span>\n <span class='hidden-xs hidden-sm'>\n <button class='btn btn-danger delete' ng-click='delete(proposal)' tooltip-html-unsafe='{{tooltips.delete}}' tooltip-placement='right'>\n <i class='fa fa-trash-o fa-2x'></i>\n </button>\n </span>\n <span class='hidden-md hidden-lg'>\n <button class='btn btn-danger delete' ng-click='delete(proposal)'>\n <i class='fa fa-trash-o fa-2x'></i>\n </button>\n </span>\n </div>\n <div class='vote_buttons_container' ng-hide='currentUser &amp;&amp; proposal.is_editable &amp;&amp; proposal.user_id == currentUser.id'>\n <span class='hidden-xs hidden-sm'>\n <button class='btn btn-support btn-bold main' ng-click='support(proposal)' tooltip-html-unsafe='{{tooltips.support}}' tooltip-placement='right'>\n <i class='fa fa-thumbs-o-up fa-2x'></i>\n </button>\n </span>\n <span class='hidden-md hidden-lg'>\n <button class='btn btn-support btn-bold main' ng-click='support(proposal)'>\n <i class='fa fa-thumbs-o-up fa-2x'></i>\n </button>\n </span>\n <span class='hidden-xs hidden-sm'>\n <button class='btn btn-improve btn-bold main' ng-click='improve(proposal)' tooltip-html-unsafe='{{tooltips.improve}}' tooltip-placement='right'>\n <i class='fa fa-code-fork fa-2x'></i>\n </button>\n </span>\n <span class='hidden-md hidden-lg'>\n <button class='btn btn-improve btn-bold main' ng-click='improve(proposal)'>\n <i class='fa fa-code-fork fa-2x'></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class='supporting_arguments'>\n <h3 ng-show='proposal.has_support'>\n Supporters\n <votes>({{ proposal.votes_count }}</votes>\n <votes count='proposal.votes_count' ng-pluralize when=\"{'0': 'Vote count error', '1': 'Vote)', 'other': 'Votes)'}\">) ({{proposal.votes_count}} votes)</votes>\n </h3>\n <div class='row support_row' ng-repeat='(key, vote) in proposal.votes'>\n <div class='proposal-person col-xs-2'>\n <div class='supporter-avatar'>\n <img ng-src='http://graph.facebook.com/{{ vote.facebook_auth }}/picture?width=30&amp;height=30' ui-if='vote.facebook_auth'>\n <img ng-src='http://gravatar.com/avatar/{{ vote.gravatar_hash }}.png?s=30&amp;d={{ sessionSettings.spokenvote_attributes.defaultGravatar }}' ui-if='!vote.facebook_auth'>\n </div>\n </div>\n <div class='support-comment col-xs-10'>\n <supporter-name>\n <a ng-click='setVoter(vote)' ng-href='#'>{{ vote.username }}</a>\n </supporter-name>\n <supporter-text>\n {{ vote.comment }}\n </supporter-text>\n <div class='supported_date'>\n {{ vote.updated_at }}\n </div>\n </div>\n </div>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/_support_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/_support_modal.html", "<div id='improveProposalModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2 class='modal-title'>Proposal Support</h2>\n <div class='selectedHub'>\n <span>\n <div class='groupName'>\n {{ sessionSettings.newSupport.target.hub.group_name }}\n </div>\n <div class='groupLocation'>\n {{ sessionSettings.newSupport.target.hub.formatted_location }}\n </div>\n </span>\n </div>\n </div>\n <div class='modal-body'>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n <form name='supportProposalForm'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>Adding support to this proposal.</h5>\n </div>\n <div class='proposal_statement'>\n <div class='form-group'>\n <h5>Proposal Statement</h5>\n <div class='originalProposal'>\n <h4>{{ sessionSettings.newSupport.target.statement }}</h4>\n </div>\n </div>\n <div class='text_input'>\n <div class='form-group'>\n <textarea class='full-width' id='vote_comment' name='vote_comment' ng-maxLength='280' ng-minLength='7' ng-model='sessionSettings.newSupport.save.comment' placeholder='Explain why you want to support this proposal.' required type='text'></textarea>\n <div class='length_counter happy-text' ng-show='supportProposalForm.vote_comment.$valid'>{{ 280 - sessionSettings.newSupport.save.comment.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='supportProposalForm.vote_comment.$invalid &amp;&amp; !supportProposalForm.vote_comment.$error.minlength &amp;&amp; !supportProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='supportProposalForm.vote_comment.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='supportProposalForm.vote_comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='saveSupport()' ng-disabled='!supportProposalForm.$valid'>Vote for this proposal</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/index.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/index.html", '<section><div class="panel panel-default hidden-xs hidden-sm"><div class="panel-title"><div class="title pull-left"><h3><span>{{ sessionSettings.routeParams.filter | inflector:humanize }} Topics for {{ sessionSettings.actions.hubFilter }}</span><span ng-click="clearFilter(&#39;user&#39;)" ui-if="sessionSettings.actions.userFilter || sessionSettings.routeParams.user"><span>for</span><span class="tag_item"><span>{{ sessionSettings.actions.userFilter || sessionSettings.routeParams.user }}</span><div class="fa fa-times"></div></span><span>&nbsp</span></span></h3><div class="list-filter"><input ng-model="q" placeholder="Filter Proposal List" type="search" /><div class="proposalsLoading" ng-show="proposalsLoading"><h3><span>Loading Proposals, please wait.</span></h3></div></div></div><h3 class="btn-group pull-right"><button btn-radio="&#39;my&#39;" class="btn btn-default" ng-click="setFilter(&#39;my&#39;)" ng-model="sessionSettings.routeParams.filter" type="button" ui-if="currentUser.id"><div class="fa sub fa-check-square-o"></div><span>My Votes</span></button><button btn-radio="&#39;active&#39;" class="btn btn-default" ng-click="setFilter(&#39;active&#39;)" ng-model="sessionSettings.routeParams.filter" type="button"><div class="fa sub fa-tachometer"></div><span>&nbsp Active</span></button><button btn-radio="&#39;recent&#39;" class="btn btn-default" ng-click="setFilter(&#39;recent&#39;)" ng-model="sessionSettings.routeParams.filter" type="button"><div class="fa sub fa-clock-o"></div><span>&nbsp Recent</span></button></h3></div><div class="clearfix"></div></div><div class="listing-proposals"><div class="tree_container" ng-click="showProposal(proposal)" ng-repeat="proposal in proposals | filter:q"><div class="topic row"><div class="list-avatar col-xs-1"><div class="avatar-sprite" ng-repeat="(key, vote) in proposal.votes | slice:0:4"><img ng-src="http://graph.facebook.com/{{ vote.vote.facebook_auth }}/picture?width=20&amp;height=20" ui-if="vote.vote.facebook_auth" /><img ng-src="http://gravatar.com/avatar/{{ vote.vote.gravatar_hash }}.png?s=30&amp;d={{ sessionSettings.spokenvote_attributes.defaultGravatar }}" ui-if="!vote.vote.facebook_auth" /></div></div><div class="topic-and-hub col-xs-9 col-md-10"><div class="proposal" id="prop{{ proposal.id }}"><a id="showProposalLink" ng-click="showProposal(proposal)">{{ proposal.statement }}</a></div><div class="hub"><a ng-click="setHub(proposal.hub)">{{ proposal.hub.short_hub }}</a></div></div><div class="stats col-xs-2 col-md-1"><div class="counts row"><div class="vote_count col-xs-6"><div class="svlabel" tooltip="{{proposal.votes_in_tree}} votes on {{proposal.related_proposals_count}} proposals" tooltip-placement="left"><div class="votes">{{ proposal.votes_in_tree }}</div><Votes></Votes></div></div><div class="prop_count col-xs-6"><div class="svlabel" tooltip="{{proposal.votes_in_tree}} votes on {{proposal.related_proposals_count}} proposals" tooltip-placement="left"><div class="props">{{ proposal.related_proposals_count }}</div><Props></Props></div></div></div><div class="last_date row"><div class="date-stamp col-xs-12">{{ proposal.updated_at }}</div></div></div></div></div></div><div class="proposalFooter" ng-show="!proposalsLoading"><div class="dont-seeit pull-right">Don\'t see the topic you\'re looking for? &nbsp<a href="#" id="makeProposalLink" ng-click="newTopic()">Start a New Topic</a></div><div class="no-proposals col-xs-12" ng-show="proposals.length == 0"><h3><span>No {{ filterSelection | inflector:humanize }} topics found&nbsp</span><span ui-if="sessionSettings.actions.hubFilter">for {{ sessionSettings.actions.hubFilter }}</span><span ui-if="sessionSettings.actions.userFilter">for {{ sessionSettings.actions.userFilter }}</span></h3></div></div></section>')
}]);
// Angular Rails Template
// source: app/assets/templates/proposals/show.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("proposals/show.html", "<div class='content_page'>\n <div id='totalVoteLabel'>\n <span class='label'>\n {{ proposal.votes_in_tree }}\n <ng-pluralize count='proposal.votes_in_tree' when=\"{'0': 'Vote count error', '1': 'Vote', 'other': 'Votes'}\"></ng-pluralize>\n on {{ proposal.related_proposals_count + 1 }}\n <ng-pluralize count='proposal.related_proposals_count + 1' when=\"{'0': 'Proposal count error', '1': 'Proposal', 'other': 'Proposals'}\"></ng-pluralize>\n </span>\n </div>\n <div class='proposal_hub'>\n <a class='hub' href='#' ng-click='hubView()'>\n <h2>{{ proposal.hub.group_name }} &ndash; {{ proposal.hub.formatted_location }}</h2>\n </a>\n </div>\n <ng-include src=\"'proposals/_show.html'\"></ng-include>\n <div class='hr'></div>\n <div id='relatedProposals' ng-controller='RelatedProposalShowCtrl'>\n <div class='related-header'>\n <div class='relatedSorter pull-right'>\n <div class='btn-group' ng-show='proposal.related_proposals_count &gt; 1'>\n <button class='btn' type='button'>{{ selectedSort || \"Sort Proposals\" }}</button>\n <button bs-dropdown='related_sorter_dropdown' class='btn' type='button'>\n <span class='caret'></span>\n </button>\n </div>\n </div>\n <div class='relatedProposalsTitle'>\n Alternative Proposals\n </div>\n </div>\n <ng-include ng-repeat='(key, proposal) in relatedProposals.related_proposals' ng-show='proposal.related_proposals_count &gt; 0' src=\"'proposals/_show.html'\"></ng-include>\n </div>\n <h3 ng-show='proposal.related_proposals_count &lt; 1'>There are no alternative proposals yet.</h3>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_alert_container.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_alert_container.html", "<div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_change_hub.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_change_hub.html", "<div class='changehub' collapse='!sessionSettings.actions.changeHub'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>Enter the group you would like to influence.</h5>\n </div>\n <div class='selecthub' ng-controller='DashboardCtrl' role='search'>\n <hidden_field_tag id='newProposalHub' ng-model='sessionSettings.actions.newProposalHub' ui-select2='hubFilterSelect2'></hidden_field_tag>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_currentuser_avatar.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_currentuser_avatar.html", "<div class='currentuser-avatar'>\n <img ng-src='http://graph.facebook.com/{{ currentUser.facebook_auth }}/picture' ui-if='currentUser.facebook_auth'>\n <img ng-src='http://gravatar.com/avatar/{{ currentUser.gravatar_hash }}.png?s=30&amp;d={{ sessionSettings.spokenvote_attributes.defaultGravatar }}' ui-if='!currentUser.facebook_auth'>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_footer.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_footer.html", '<h4><a href="/landing">Home</a><a href="/user-forum">User Forum</a><a href="/dev-forum">Developer Forum</a><a href="/proposals?filter=active">Most Active Proposals</a><a href="/proposals?filter=recent">Most Recent Proposals</a><a href="/terms-of-use">Terms of Use</a></h4>')
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_get_started_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_get_started_modal.html", "<div id='getStartedModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2 ng-show='sessionSettings.actions.changeHub == true'>Step 1 - Find Group</h2>\n <h2 ng-show='!sessionSettings.actions.changeHub &amp;&amp; sessionSettings.hub_attributes.id'>Step 2 - Find Topic</h2>\n <h2 ng-show='sessionSettings.actions.changeHub == \"new\"'>Step 2 - New Group</h2>\n <h2 ng-show='!sessionSettings.actions.changeHub &amp;&amp; !sessionSettings.hub_attributes.id'>Step 3 - New Topic</h2>\n <ng-include src=\"'shared/_selected_hub.html'\"></ng-include>\n </div>\n <div class='modal-body'>\n <ng-include src=\"'shared/_alert_container.html'\"></ng-include>\n <div class='instructions' ng-show='!sessionSettings.actions.changeHub &amp;&amp; sessionSettings.hub_attributes.id &amp;&amp; !sessionSettings.actions.wizardToGroup'>\n <h3>\n <span>We found</span>\n <span ng-click='changeHub(true)'>your group!</span>\n </h3>\n <h5 class='hint-text'>\n <div class='fa fa-lightbulb-o'></div>\n <span-red>Hint!</span-red>\n <span>If the group shown above is not the group you wanted, just click the name to search again.</span>\n </h5>\n </div>\n <div class='instructions' ng-show='!sessionSettings.actions.changeHub &amp;&amp; sessionSettings.hub_attributes.id &amp;&amp; sessionSettings.actions.wizardToGroup'>\n <h3>\n <span>Going to</span>\n <span ng-click='changeHub(true)'>{{ sessionSettings.hub_attributes.group_name }} ...</span>\n </h3>\n <h5 class='hint-text'>\n <div class='fa fa-lightbulb-o'></div>\n <span-red>Hint!</span-red>\n <span>If you don't see the Topic you want on the next screen, just select Start New Topic.</span>\n </h5>\n </div>\n <form name='newProposalForm' ng-submit='saveNewProposal()'>\n <div class='newProposal'>\n <ng-include src=\"'shared/_change_hub.html'\"></ng-include>\n <ng-include src=\"'shared/_make_hub.html'\"></ng-include>\n <ng-include ng-hide='sessionSettings.hub_attributes.id' src=\"'shared/_proposal_area.html'\"></ng-include>\n </div>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='votingService.saveNewProposal(modalInstance)' ng-disabled='!newProposalForm.$valid' ng-show='sessionSettings.actions.changeHub == false &amp;&amp; !sessionSettings.hub_attributes.id'>Save your New proposal</button>\n <button class='btn btn-primary btn-bold' ng-click='sessionSettings.actions.changeHub = false' ng-disabled='!newProposalForm.formatted_location.$valid || !newProposalForm.group_name.$valid' ng-show='sessionSettings.actions.changeHub == \"new\"'>Continue</button>\n <button class='btn btn-primary btn-bold' ng-click='goToGroup(\"hint\")' ng-show='sessionSettings.hub_attributes.id &amp;&amp; !sessionSettings.actions.wizardToGroup'>Go to {{ sessionSettings.hub_attributes.group_name }}</button>\n <button class='btn btn-primary btn-bold' ng-click='$close()' ng-show='sessionSettings.hub_attributes.id &amp;&amp; sessionSettings.actions.wizardToGroup == \"hint\"'>Ok, got it. Go to {{ sessionSettings.hub_attributes.group_name }}.</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_make_hub.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_make_hub.html", "<div class='makeHub' ui-if='sessionSettings.actions.changeHub == \"new\"'>\n <h5 class='hint-text'>\n <div class='fa fa-lightbulb-o'></div>\n <span-red>Hint!</span-red>\n <span>Double-check to make sure your group does not already exist by trying alternate spellings above.</span>\n </h5>\n <h3>Create new group</h3>\n <div class='input-area'>\n <input class='input-xlarge' id='group_name' name='group_name' ng-maxLength='50' ng-minlength='3' ng-model='sessionSettings.actions.searchTerm' placeholder='Enter your new group name' type='text'>\n <div class='length_counter happy-text' ng-show='newProposalForm.group_name.$valid'>{{ 50 - sessionSettings.actions.searchTerm.length }} more characters allowed</div>\n <div class='length_counter error-text' ng-show='newProposalForm.group_name.$invalid &amp;&amp; !newProposalForm.group_name.$error.minlength &amp;&amp; !newProposalForm.group_name.$error.maxlength'>\n <span>Required</span>\n <div class='fa fa-warning'></div>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.group_name.$error.minlength'>\n <span>Too Short</span>\n <div class='fa fa-warning'></div>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.group_name.$error.maxlength'>\n <spanToo>Long</spanToo>\n <div class='fa fa-warning'></div>\n </div>\n <input class='input-xlarge' id='hub_formatted_location' name='formatted_location' ng-model='sessionSettings.hub_attributes.formatted_location' placeholder=\"Enter the Group's Location\" required sv-google-place type='text'>\n <div class='length_counter error-text' ng-hide='newProposalForm.formatted_location.$valid'>\n <span>Choose a valid location from list.</span>\n <div class='fa fa-warning'></div>\n </div>\n <div class='length_counter happy-text' ng-show='newProposalForm.formatted_location.$valid &amp;&amp; newProposalForm.group_name.$valid'>\n <a class='link' href='#' ng-click='sessionSettings.actions.changeHub = false'>Group Location Valid</a>\n </div>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_navigation.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_navigation.html", '<div id="topBar" ng-controller="DashboardCtrl"><header class="navbar navbar-default hidden-md hidden-lg"><div class="navbar-header"><button class="navbar-toggle pull-left" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" ng-hide="sessionSettings.actions.detailPage == true" type="button"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><div class="fa fa-angle-left fa-2x navbar-toggle pull-left" ng-click="backtoTopics()" ng-href="#" ng-show="sessionSettings.actions.detailPage == true" type="button"></div><a class="sv-logo" href="/landing"><span class="spoken">Spoken</span><span class="vote">Vote</span></a><div class="new-proposal pull-right" ng-click="newTopic()"><button class="navbar-toggle" type="button"><div class="fa fa-pencil-square-o fa-2x"></div></button></div><div class="hub_search-xs" role="search"><hidden_field_tag id="hub_filter" ng-model="hubFilter.hubFilter" ui-select2="hubFilterSelect2"></hidden_field_tag></div></div></header><header class="navbar navbar-inverse navbar-fixed-top hidden-xs hidden-sm"><div class="container"><div class="row"><div class="navbar-header col-xs-2"><div class="navbar-brand"><a class="sv-logo" href="/landing"><span class="spoken">Spoken</span><span class="vote">Vote</span></a></div></div><div class="hub_search col-xs-5" role="search"><div class="navbar-form"><hidden_field_tag id="hub_filter" ng-model="hubFilter.hubFilter" ui-select2="hubFilterSelect2"></hidden_field_tag></div></div><div class="new_topic navbar-btn col-xs-2"><button class="btn btn-newTopic btn-bold main" ng-click="newTopic()"> Start New Topic</button></div><div class="col-xs-2"><ul class="nav navbar-nav pull-right" id="user_profile"><li class="dropdown" data-placement="bottom"><a class="dropdown-toggle" data-toggle="dropdown"><b class="white">{{ currentUser.first_name || \'Learn\' }}</b><b class="caret"></b></a><ul aria-labelledby="dropdownMenu" class="dropdown-menu" role="menu"><li><a ng-href="/proposals?filter=my" ng-show="currentUser.username"> My Proposals</a></li><li><a href="#" ng-click="userSettings()" ng-show="currentUser.username"> Settings</a></li><li><a href="/admin" ng-show="currentUser[&quot;is_admin?&quot;]" target="_blank"> Admin</a></li><li><a class="divider" ng-show="currentUser.username"></a></li><li><a href="#" ng-click="signOut()" ng-show="currentUser.username"> Sign Out</a></li><li><a class="divider" ng-show="currentUser.username"></a></li><li><a href="/user-forum" target="_blank">User Forum</a></li><li><a href="/dev-forum" target="_blank">Developer Forum</a></li><li><a href="https://github.com/Spokenvote/spokenvote/issues?state=open" target="_blank">Open Issues</a></li><li><a class="divider"></a></li><li><a href="http://spokenvote.github.io/spokenvote/" target="_blank">Github Page</a></li><li><a class="divider"></a></li><li><a href="/terms-of-use">Terms of Use</a></li></ul></li></ul></div><div class="user_area navbar-btn col-xs-1"><div class="avatar pull-right" ng-href="/proposals?filter=my" ng-show="currentUser.facebook_auth || currentUser.gravatar_hash" tooltip="{{currentUser.username}}" tooltip-placement="bottom"><a href="/proposals?filter=my"><ng-include src="&#39;shared/_currentuser_avatar.html&#39;"></ng-include></a></div><div class="buttons navbar-btn pull-right" ng-show="!currentUser.username"><button class="btn btn-info btn-bold" ng-click="signinAuth()"> Sign In</button></div></div></div></div></header></div>')
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_proposal_area.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_proposal_area.html", "<div class='proposal-area' ng-hide='sessionSettings.actions.changeHub'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>Enter your proposal and explain why others should support it!</h5>\n </div>\n <div class='form-group'>\n <textarea class='full-width' id='proposal_statement' name='statement' ng-maxLength='140' ng-minLength='15' ng-model='sessionSettings.newProposal.statement' placeholder='Write your main proposal statement here.' required></textarea>\n <div class='length_counter happy-text' ng-show='newProposalForm.statement.$valid'>{{ 140 - sessionSettings.newProposal.statement.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='newProposalForm.statement.$invalid &amp;&amp; !newProposalForm.statement.$error.minlength &amp;&amp; !newProposalForm.statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.statement.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.statement.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n <div class='form-group'>\n <textarea class='full-width' id='vote_comment' name='comment' ng-maxLength='280' ng-minLength='7' ng-model='sessionSettings.newProposal.comment' placeholder='Explain why others should support your proposal here.' required></textarea>\n <div class='length_counter happy-text' ng-show='newProposalForm.comment.$valid'>{{ 280 - sessionSettings.newProposal.comment.length }} characters remaining</div>\n <div class='length_counter error-text' ng-show='newProposalForm.comment.$invalid &amp;&amp; !newProposalForm.comment.$error.minlength &amp;&amp; !newProposalForm.comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Required</span>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.comment.$error.minlength'>\n <div class='fa fa-warning'></div>\n <span>Too Short</span>\n </div>\n <div class='length_counter error-text' ng-show='newProposalForm.comment.$error.maxlength'>\n <div class='fa fa-warning'></div>\n <span>Too Long</span>\n </div>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_registration_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_registration_modal.html", "<div class='modal' id='registrationModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='close()'>&times;</div>\n <h3>Join SpokenVote</h3>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n </div>\n <div class='modal-body'>\n <div class='login_providers'>\n <div class='content_div'>\n <div class='fb-button-container-centered'>\n <div class='fb-button' ng-click=\"userOmniauth('facebook')\">\n <span class='fb-button-left'></span>\n <span class='fb-button-center'>\n <strong>Sign in</strong>\n with\n <strong>Facebook</strong>\n </span>\n <span class='fb-button-right'></span>\n </div>\n </div>\n </div>\n </div>\n <h3 class='or-separator'>or</h3>\n <div class='login_form'>\n <form name='registrationForm' ng-submit='register()' novalidate>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='name' name='name' ng-minLength='4' ng-model='registration.name' placeholder='Name' required type='text'>\n <div class='input-below-text' ng-show='registrationForm.name.$invalid &amp;&amp; registrationForm.name.$dirty'>Minimum length of 4</div>\n </input>\n </div>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='email_field' name='email' ng-minLength='3' ng-model='registration.email' placeholder='Email address' required type='email'>\n </div>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='password_field' name='password' ng-minLength='5' ng-model='registration.password' placeholder='Password' required type='password'>\n <div class='input-below-text' ng-show='registrationForm.password.$invalid &amp;&amp; registrationForm.password.$dirty'>Minimum length of 5</div>\n </div>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='password_confirmation_field' name='password_confirmation' ng-minLength='5' ng-model='registration.password_confirmation' placeholder='Confirm password' required type='password'>\n <div class='input-below-text' ng-show='registration.password != registration.password_confirmation &amp;&amp; registrationForm.password_confirmation.$dirty || registrationForm.$invalid &amp;&amp; registrationForm.password_confirmation.$dirty '>Almost ...</div>\n <div class='input-below-text' ng-show='registrationForm.$valid &amp;&amp; registration.password == registration.password_confirmation &amp;&amp; registrationForm.password_confirmation.$dirty'>Match!</div>\n </div>\n </div>\n <input class='invisible' type='submit'>\n </form>\n </div>\n </div>\n <div class='modal-footer'>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='register()' ng-disabled='!registrationForm.$valid || registration.password != registration.password_confirmation'>Join SpokenVote</button>\n </div>\n </div>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_request_response_partial.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_request_response_partial.html", "<div class='alert alert-bar' ng-class='alertService.alertClass' ng-show='alertService.alertClass'>\n <button class='close' ng-click='hideAlert()' type='button'>&times;</button>\n <ul-list-style-type-none>\n <li>{{ alertService.cltActionResult }}</li>\n <li>{{ alertService.alertMessage }}</li>\n <span ng-repeat='(key, values) in alertService.jsonErrors'>\n <li ng-repeat='value in values' ng-show='alertService.jsonErrors'>\n {{ key | capitalize }} {{ value }}\n </li>\n </span>\n </ul-list-style-type-none>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_selected_hub.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_selected_hub.html", "<div class='selectedHub'>\n <span href='#' ng-click='votingService.changeHub(true)' ng-show='sessionSettings.hub_attributes.id || sessionSettings.actions.searchTerm'>\n <div class='groupName' ng-show='sessionSettings.hub_attributes.id'>\n <i class='glyphicon glyphicon-pencil'></i>\n <span tooltip-html-unsafe='{{ rootTips.newHub }}' tooltip-placement='left'>{{ sessionSettings.hub_attributes.group_name }}</span>\n </div>\n <div class='groupName' ng-show='!sessionSettings.hub_attributes.id &amp;&amp; sessionSettings.hub_attributes.location_id'>\n <i class='glyphicon glyphicon-pencil'></i>\n <span tooltip-html-unsafe='{{ rootTips.newHub }}' tooltip-placement='left'>{{ sessionSettings.actions.searchTerm }}</span>\n </div>\n <div class='groupLocation' show='sessionSettings.hub_attributes.formatted_location'>{{ sessionSettings.hub_attributes.formatted_location }}</div>\n </span>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_sidebar.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_sidebar.html", '<ul ng-controller="SidebarCtrl" role="menu"><li><a class="action-links"><div class="fa fa-file-text-o"></div><span ng-click="newTopic();sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" tooltip="{{tooltips.newTopic}}" tooltip-placement="bottom">Start a new topic</span></a></li><li><accordion close-others="oneAtATime"><accordion-group is-open="isopen"><accordion-heading><a class="action-links"><div class="fa fa-filter"></div><span ng-show="isopen"> Topic filter</span><span ng-hide="isopen"> Filter: {{ sessionSettings.routeParams.filter | inflector:humanize }} votes</span></a></accordion-heading><ul class="action-links"><li ng-class="{&#39;active&#39;: sessionSettings.routeParams.filter == &#39;my&#39;}" ng-click="setFilter(&#39;my&#39;);sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" ng-show="currentUser.id" type="button"><div class="fa sub fa-check-square-o"></div>My Votes<div class="fa pull-right" ng-class="{&#39;fa-flag-o&#39;: sessionSettings.routeParams.filter == &#39;my&#39;}"></div></li><li ng-class="{&#39;active&#39;: sessionSettings.routeParams.filter == &#39;active&#39;}" ng-click="setFilter(&#39;active&#39;);sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" type="button"><div class="fa sub fa-tachometer"></div>Active<div class="fa pull-right" ng-class="{&#39;fa-flag-o&#39;: sessionSettings.routeParams.filter == &#39;active&#39;}"></div></li><li ng-class="{&#39;active&#39;: sessionSettings.routeParams.filter == &#39;new&#39;}" ng-click="setFilter(&#39;recent&#39;);sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" type="button"><div class="fa sub fa-clock-o"></div>Recent<div class="fa pull-right" ng-class="{&#39;fa-flag-o&#39;: sessionSettings.routeParams.filter == &#39;new&#39;}"></div></li></ul></accordion-group></accordion></li><li href="#" ng-click="userSettings();sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" ng-show="currentUser.username"><a class="action-links"><div class="fa fa-wrench"></div>Settings</a></li><li href="#" ng-click="signinAuth();sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" ng-show="!currentUser.username"><a class="action-links"><div class="fa fa-sign-in"></div>Sign in</a></li><li href="#" ng-click="signOut();sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" ng-show="currentUser.username"><a class="action-links"><div class="fa fa-sign-out"></div>Sign out</a></li><li ng-show="currentUser[&quot;is_admin?&quot;]"><a class="action-links" href="/admin" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" target="_blank"><div class="fa fa-cogs"></div>Admin</a></li><li><div class="hr"></div></li><li><accordion close-others="oneAtATime"><accordion-group is-open="isopen"><accordion-heading><a class="action-links"><div class="fa fa-group"></div>Community</a></accordion-heading><ul class="action-links"><li><a class="category-links" href="/user-forum" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" target="_blank"><div class="fa fa-comments"></div>User Forum</a></li><li><a class="category-links" href="/dev-forum" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" target="_blank"><div class="fa fa-stack-overflow"></div>Developer Forum</a></li><li><a class="category-links" href="https://github.com/Spokenvote/spokenvote/issues?state=open" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" target="_blank"><div class="fa fa-bug"></div>Open Issues</a></li></ul></accordion-group></accordion></li><li><div class="hr"></div></li><li><a class="category-links" href="http://spokenvote.github.io/spokenvote/" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas" target="_blank"><div class="fa fa-github"></div>Github Page</a></li><li class="category-links"><div class="hr"></div></li><li><a class="category-links" href="/terms-of-use" ng-click="sessionSettings.actions.offcanvas=!sessionSettings.actions.offcanvas"><div class="fa fa-bell-o"></div>Terms of use</a></li></ul>')
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_sign_in_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_sign_in_modal.html", "<div class='modal' data-width='300px' id='loginModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='close()'>&times;</div>\n <h3>Please Sign In</h3>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n </div>\n <div class='modal-body'>\n <div class='login_providers'>\n <div class='content_div'>\n <div class='fb-button-container-centered'>\n <div class='fb-button' ng-click=\"userOmniauth('facebook')\">\n <span class='fb-button-left'></span>\n <span class='fb-button-center'>\n <strong>Sign in</strong>\n with\n <strong>Facebook</strong>\n </span>\n <span class='fb-button-right'></span>\n </div>\n </div>\n </div>\n </div>\n <h3 class='or-separator'>or</h3>\n <div class='login_form'>\n <form name='signInForm' ng-submit='signIn()'>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='email_field' ng-minLength='3' ng-model-instant ng-model='session.email' placeholder='Email address' required type='text'>\n </div>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <input class='full-width' id='password_field' ng-minLength='3' ng-model-instant ng-model='session.password' placeholder='Password' required type='password'>\n <span></span>\n </div>\n <div class='form-group'>\n <div class='controls'>\n <label class='checkbox'>\n <input id='remember_me' ng-model='session.remember_me' type='checkbox'>Remember me next time</input>\n </label>\n </div>\n </div>\n <input class='invisible' type='submit'>\n </div>\n </form>\n </div>\n </div>\n <div class='modal-footer'>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-warning btn-bold pull-left' ng-click='registerModal();close()'>Need to Sign Up?</button>\n <button class='btn btn-primary btn-bold' ng-click='signIn()' ng-disabled='!signInForm.$valid'>Sign In</button>\n </div>\n </div>\n </div>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_social_buttons.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_social_buttons.html", "<div class='shares btn-group'>\n <a class='btn btn-xs' ng-href='{{ socialSharing.twitterUrl }}' tooltip-html-unsafe='{{tooltips.twitter}}'><i class='fa fa-twitter'></i></a>\n <a class='btn btn-xs' ng-href='{{ socialSharing.facebookUrl }}' tooltip-html-unsafe='{{tooltips.facebook}}'><i class='fa fa-facebook'></i></a>\n <a class='btn btn-xs' ng-href='{{ socialSharing.googleUrl }}' tooltip-html-unsafe='{{tooltips.google}}'><i class='fa fa-google-plus'></i></a>\n</div>")
}]);
// Angular Rails Template
// source: app/assets/templates/shared/_user_nav.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("shared/_user_nav.html", "<li class='dropdown' id='user_menu'>\n <span bs-dropdown='user_dropdown' class='dropdown-toggle' type='button'><b class=\"white\"> {{ currentUser.username }} </b> <b class=\"caret\"></b></span>\n <!-- the data-email attribute is not a good way to have user on hand but acceptable to me for first pass -->\n</li>")
}]);
// Angular Rails Template
// source: app/assets/templates/user/_auth_intro_modal.html.slim
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("user/_auth_intro_modal.html", '<div id="introAuthModal"><div class="modal-header"><div class="pull-right close" ng-click="$close()">&times;</div></div><div class="modal-body"><div id="alertContainer"><div alert-bar="" alertmessageclear="alertService.alertClass" class="modalAlert" ng-show="alertService.alertDestination==&quot;modal&quot;"></div></div><div class="auth-partners"><div class="instructions"><div class="partner-logo"><img src="/assets/icons/providers/FB-f-Logo__blue_50.png" /></div><h4>Simplified Facebook sign in helps prevent vote duplication.</h4><h4>We will <i>not</i> use your friends list or post anything without asking you first.</h4></div></div></div><div class="modal-footer"><fieldset><div class="form-group"><div class="controls"><button class="btn btn-bold" ng-click="$dismiss(&#39;cancel&#39;)">Cancel</button><button class="btn btn-primary btn-bold" ng-click="$close(&#39;signIn&#39;)">Sign In</button></div></div></fieldset></div></div>')
}]);
// Angular Rails Template
// source: app/assets/templates/user/_settings_modal.html.haml
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("user/_settings_modal.html", "<div id='userSettingsModal'>\n <div class='modal-header'>\n <div class='close pull-right' ng-click='$close()'>&times;</div>\n <h2>My Settings</h2>\n <div class='selectedHub'>\n <div class='hub'>\n <h3>{{ currentUser.name }}</h3>\n <h5>{{ sessionSettings.facebookUser.me.location.name }}</h5>\n </div>\n </div>\n <div id='alertContainer'>\n <div alert-bar alertmessageclear='alertService.alertClass' class='modalAlert' ng-show='alertService.alertDestination==\"modal\"'></div>\n </div>\n </div>\n <div class='modal-body'>\n <form name='userSettingsForm'>\n <div class='instructions'>\n <div class='avatar'>\n <ng-include src=\"'shared/_currentuser_avatar.html'\"></ng-include>\n </div>\n <h5>At this time your settings all come from Facebook and should be edited there. Very soon some awesome social graph features will be added, and this window is where you will configure them.</h5>\n </div>\n <input class='half-width' id='user_full_name' ng-disabled='true' ng-maxLength='50' ng-minLength='5' ng-model='currentUser.name' placeholder='Your full name' required type='text'>\n <input class='half-width' id='user_email' ng-disabled='true' ng-maxLength='50' ng-minLength='5' ng-model='currentUser.email' placeholder='Your primary email' required type='text'>\n <input class='thee_quarter-width' id='user_location' ng-disabled='true' ng-maxLength='50' ng-minLength='5' ng-model='sessionSettings.facebookUser.me.location.name' placeholder='Your city' required type='text'>\n <input class='thee_quarter-width' id='user_hometown' ng-disabled='true' ng-maxLength='50' ng-minLength='5' ng-model='sessionSettings.facebookUser.me.hometown.name' placeholder='Your hometown' required type='text'>\n </form>\n </div>\n <div class='modal-footer'>\n <fieldset>\n <div class='form-group'>\n <div class='controls'>\n <button class='btn btn-primary btn-bold' ng-click='saveUserSettings()' ng-disabled='userSettingsForm.$valid || userSettingsForm.$invalid'>Save Settings</button>\n </div>\n </div>\n </fieldset>\n </div>\n</div>")
}]);
(function() {
'use strict';
var appConfig, servicesConfig;
appConfig = [
'$routeProvider', '$locationProvider', '$httpProvider', '$modalProvider', function($routeProvider, $locationProvider, $httpProvider, $modalProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$locationProvider.html5Mode(true);
$routeProvider.when('/', {
title: 'Online Group Consensus Tool',
templateUrl: 'pages/landing.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.params.filter, $route.current.title);
}
]
}
}).when('/landing', {
title: 'Online Group Consensus Tool',
templateUrl: 'pages/landing.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.params.filter, $route.current.title);
}
]
}
}).when('/proposals', {
title: 'Proposals',
templateUrl: 'proposals/index.html',
controller: 'ProposalListCtrl',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.params.filter, $route.current.title);
}
]
}
}).when('/proposals/:proposalId', {
title: 'Proposal',
templateUrl: 'proposals/show.html',
controller: 'ProposalShowCtrl',
resolve: {
proposal: [
'ProposalLoader', function(ProposalLoader) {
return ProposalLoader();
}
],
relatedProposals: [
'RelatedProposalsLoader', function(RelatedProposalsLoader) {
return RelatedProposalsLoader();
}
],
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
console.log('$route.current: ', $route.current);
return $rootScope.page.setTitle($route.current.title, $route.current.params.proposalId);
}
]
}
}).when('/user-forum', {
title: 'User Forum',
templateUrl: 'pages/user-forum.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.title);
}
]
}
}).when('/dev-forum', {
title: 'Developer Forum',
templateUrl: 'pages/dev-forum.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.title);
}
]
}
}).when('/terms-of-use', {
title: 'Terms of Use',
templateUrl: 'pages/terms-of-use.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.title);
}
]
}
}).when('/privacy', {
title: 'Privacy Policy',
templateUrl: 'pages/privacy.html',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.title);
}
]
}
}).otherwise({
title: 'Lost in Space',
template: '<h3>Whoops, page not found</h3>',
resolve: {
pageTitle: [
'$rootScope', '$route', function($rootScope, $route) {
return $rootScope.page.setTitle($route.current.title);
}
]
}
});
$modalProvider.options = {
backdrop: true,
keyboard: true,
windowClass: ''
};
return jQuery(function() {
$('body').prepend('<div id="fb-root"></div>');
return $.ajax({
url: "" + window.location.protocol + "//connect.facebook.net/en_US/all.js",
dataType: 'script',
cache: true
});
});
}
];
window.App = angular.module('spokenvote', ['ngRoute', 'angular-loading-bar', 'ngAnimate', 'spokenvote.services', 'spokenvote.directives', 'templates', 'ui', 'ui.bootstrap']).config(appConfig);
servicesConfig = [
'$httpProvider', function($httpProvider) {
return $httpProvider.responseInterceptors.push('errorHttpInterceptor');
}
];
App.Services = angular.module('spokenvote.services', ['ngResource', 'ngCookies']).config(servicesConfig).run([
'$rootScope', '$location', function($rootScope, $location) {
$rootScope.location = $location;
return $rootScope.page = {
prefix: '',
body: 'Online Group Consensus Tool' + ' | ',
brand: 'Spokenvote',
setTitle: function(prefix, body) {
prefix = prefix ? prefix.charAt(0).toUpperCase() + prefix.substring(1) + ' | ' : this.prefix;
body = body ? body.charAt(0).toUpperCase() + body.substring(1) + ' | ' : this.body;
return this.title = prefix + body + this.brand;
}
};
}
]);
App.Directives = angular.module('spokenvote.directives', []);
window.getSrv = function(name, element) {
element = element || "*[ng-app]";
return angular.element(element).injector().get(name);
};
window.getScope = function(element) {
return angular.element(element).scope();
};
}).call(this);
//@ sourceMappingURL=spokenvote.map(function() {
var DashboardCtrl;
DashboardCtrl = [
'$scope', '$route', '$location', 'CurrentHubLoader', function($scope, $route, $location, CurrentHubLoader) {
$scope.sessionSettings.routeParams = $route.current.params;
$scope.hubFilter = {
hubFilter: null
};
if (($route.current.params.hub != null) && ($route.current.params.proposalId == null)) {
$scope.hubFilter = {
hubFilter: true
};
}
$scope.$on('$locationChangeSuccess', function() {
if (($route.current.params.hub != null) && ($scope.hubFilter.hubFilter === null || (String($scope.hubFilter.hubFilter.select_id) !== String($route.current.params.hub)))) {
return CurrentHubLoader().then(function(paramHub) {
$scope.sessionSettings.hub_attributes = paramHub;
$scope.sessionSettings.hub_attributes.id = $scope.sessionSettings.hub_attributes.select_id;
return $scope.hubFilter.hubFilter = $scope.sessionSettings.hub_attributes;
});
} else if ($route.current.params.hub == null) {
return $scope.hubFilter.hubFilter = null;
}
});
$scope.$watch('hubFilter.hubFilter', function() {
if ($scope.hubFilter.hubFilter === null) {
if ($location.path() === '/proposals') {
$location.search('hub', null);
}
return $scope.sessionSettings.actions.hubFilter = 'All Groups';
} else if (($scope.sessionSettings.hub_attributes.id != null) && $scope.sessionSettings.actions.selectHub === true) {
$scope.sessionSettings.actions.selectHub = false;
$location.path('/proposals').search('hub', $scope.sessionSettings.hub_attributes.id);
return $scope.sessionSettings.actions.hubFilter = $scope.sessionSettings.hub_attributes.short_hub;
}
});
$scope.hubFilterSelect2 = {
minimumInputLength: 1,
minimumResultsForSearch: -1,
placeholder: "<div class='fa fa-search'></div>" + "<span> Find your Group or Location</span>",
width: '100%',
allowClear: true,
ajax: {
url: "/hubs",
dataType: "json",
data: function(term, page) {
return {
hub_filter: term
};
},
results: function(data, page) {
return {
results: data
};
}
},
createSearchChoice: function(term) {
if (term.length > 2) {
return {
id: -1,
select_id: -1,
term: term,
full_hub: term + ' (Create New Group)'
};
}
},
escapeMarkup: function(m) {
return m;
},
formatResult: function(searchedHub) {
return searchedHub.full_hub;
},
formatSelection: function(searchedHub) {
var currentHub;
if (searchedHub.id === -1) {
console.log(searchedHub);
$scope.sessionSettings.actions.searchTerm = searchedHub.term;
currentHub = $scope.sessionSettings.hub_attributes;
$scope.sessionSettings.hub_attributes = {};
$scope.sessionSettings.hub_attributes.location_id = currentHub.location_id;
$scope.sessionSettings.hub_attributes.formatted_location = currentHub.formatted_location;
angular.element('.select2-dropdown-open').select2('close');
angular.element('#newProposalHub').select2('data', null);
if ($scope.currentUser.id == null) {
$scope.authService.signinFb($scope).then(function() {
if (!$scope.sessionSettings.openModals.newProposal && !$scope.sessionSettings.openModals.getStarted) {
$scope.votingService["new"]();
$scope.sessionSettings.openModals.newProposal = true;
}
return $scope.sessionSettings.actions.changeHub = 'new';
});
} else {
if (!$scope.sessionSettings.openModals.newProposal && !$scope.sessionSettings.openModals.getStarted) {
$scope.votingService["new"]();
}
$scope.sessionSettings.openModals.newProposal = true;
$scope.sessionSettings.actions.changeHub = 'new';
}
return searchedHub.term;
} else if (!_.isEmpty(searchedHub)) {
console.log('else');
$scope.sessionSettings.hub_attributes = searchedHub;
$scope.sessionSettings.actions.changeHub = false;
$scope.sessionSettings.actions.selectHub = true;
$scope.sessionSettings.hub_attributes.id = $scope.sessionSettings.hub_attributes.select_id;
$scope.hubFilter.hubFilter = searchedHub;
return searchedHub.full_hub;
}
},
formatNoMatches: function(term) {
$scope.sessionSettings.actions.searchTerm = term;
return 'Not here? <a id="navCreateHub" onclick="App.navCreateHub()" href="javascript:" >Click here to create it</a>.';
},
id: function(obj) {
return obj.select_id;
},
initSelection: function(element, callback) {
if ($scope.sessionSettings.actions.changeHub === "new") {
return callback({});
} else {
return CurrentHubLoader().then(function(searchedHub) {
if (!_.isEmpty(searchedHub)) {
$scope.sessionSettings.hub_attributes = searchedHub;
}
return callback($scope.sessionSettings.hub_attributes);
});
}
}
};
App.navCreateHub = function() {
return $scope.$apply(function() {
var currentHub;
currentHub = $scope.sessionSettings.hub_attributes;
$scope.sessionSettings.hub_attributes = {};
$scope.sessionSettings.hub_attributes.location_id = currentHub.location_id;
$scope.sessionSettings.hub_attributes.formatted_location = currentHub.formatted_location;
angular.element('.select2-dropdown-open').select2('close');
angular.element('#newProposalHub').select2('data', null);
if ($scope.currentUser.id == null) {
return $scope.authService.signinFb($scope).then(function() {
if (!$scope.sessionSettings.openModals.newProposal && !$scope.sessionSettings.openModals.getStarted) {
$scope.votingService["new"]();
}
return $scope.sessionSettings.actions.changeHub = 'new';
});
} else {
if (!$scope.sessionSettings.openModals.newProposal && !$scope.sessionSettings.openModals.getStarted) {
$scope.votingService["new"]();
}
return $scope.sessionSettings.actions.changeHub = 'new';
}
});
};
return $scope.tooltips = {
navMenu: 'Menu',
backtoTopics: 'Return to Topic list',
newTopic: 'Start a New Topic'
};
}
];
App.controller('DashboardCtrl', DashboardCtrl);
}).call(this);
//@ sourceMappingURL=dashboard_controller.map(function() {
var ProposalListCtrl, ProposalShowCtrl, RelatedProposalShowCtrl;
ProposalListCtrl = [
'$scope', '$location', 'MultiProposalLoader', 'SpokenvoteCookies', function($scope, $location, MultiProposalLoader, SpokenvoteCookies) {
$scope.proposalsLoading = true;
MultiProposalLoader().then(function(proposals) {
$scope.proposals = proposals;
return $scope.proposalsLoading = false;
});
$scope.spokenvoteSession = SpokenvoteCookies;
$scope.sessionSettings.actions.detailPage = false;
$scope.setFilter = function(filterSelected) {
return $location.search('filter', filterSelected);
};
$scope.setHub = function(hubSelected) {
return $location.path('/proposals/').search('hub', hubSelected.id);
};
return $scope.$on('event:proposalsChanged', function() {
return $scope.proposals.$query;
});
}
];
ProposalShowCtrl = [
'$scope', '$location', 'proposal', 'relatedProposals', function($scope, $location, proposal, relatedProposals) {
$scope.proposal = proposal;
$scope.relatedProposals = relatedProposals;
$scope.sessionSettings.actions.detailPage = true;
$scope.$on('event:votesChanged', function() {
return $scope.proposal.$get();
});
$scope.hubView = function() {
return $location.path('/proposals').search('hub', proposal.hub.id);
};
$scope.setVoter = function(vote) {
$location.path('/proposals').search('user', vote.user_id);
return $scope.sessionSettings.actions.userFilter = vote.username;
};
$scope.support = function(clicked_proposal) {
if ($scope.currentUser.id != null) {
return $scope.votingService.support(clicked_proposal);
} else {
return $scope.authService.signinFb($scope).then(function() {
return $scope.votingService.support(clicked_proposal);
});
}
};
$scope.improve = function(clicked_proposal) {
if ($scope.currentUser.id != null) {
return $scope.votingService.improve($scope, clicked_proposal);
} else {
return $scope.authService.signinFb($scope).then(function() {
return $scope.votingService.improve($scope, clicked_proposal);
});
}
};
$scope.edit = function(clicked_proposal) {
return $scope.votingService.edit($scope, clicked_proposal);
};
$scope["delete"] = function(clicked_proposal) {
return $scope.votingService["delete"]($scope, clicked_proposal);
};
$scope.tooltips = {
support: "<h6><b>Support this proposal</b></h6><b>Supporting:</b> You may support only one proposal on this topic, but are free to change your support to a <i>different</i> proposal at any time by clicking <i>support</i> on that proposal or by composing an <i>improved</i> proposal.",
improve: "<h6><b>Create a better proposal</b></h6><b>Improving:</b> By composing an <i>improved</i> proposal you automatically become that proposal's first supporter. You may change your support to a <i>different</i> proposal at any time by supporting it or by composing another <i>improved</i> proposal.",
edit: "<h6><b>Edit your proposal</b></h6><b>Editing: </b>You may edit your proposal<br /> up until it receives its first support from<br />another user.",
"delete": "<h6><b>Delete your proposal</b></h6><b>Deleting: </b>You may delete your<br />proposal up until it receives its first<br />support from another user or if support<br /> ever falls to zero.",
twitter: 'Share this proposal on Twitter',
facebook: 'Share this proposal on Facebook',
google: 'Share this proposal on Google+'
};
return $scope.socialSharing = {
twitterUrl: $scope.sessionSettings.socialSharing.twitterRootUrl + 'Check out this Spokenvote proposal:' + $scope.location.absUrl() + ' /via @spokenvote',
facebookUrl: $scope.sessionSettings.socialSharing.facebookRootUrl + $scope.location.absUrl(),
googleUrl: $scope.sessionSettings.socialSharing.googleRootUrl + $scope.location.absUrl()
};
}
];
RelatedProposalShowCtrl = [
'$scope', function($scope) {
$scope.$on('event:votesChanged', function() {
return $scope.relatedProposals.$get();
});
$scope.related_sorter_dropdown = [
{
text: "By Votes",
submenu: [
{
text: "Most Votes",
click: "sortRelatedProposals('Most Votes')"
}, {
text: "Least Votes",
click: "sortRelatedProposals('Least Votes')"
}
]
}, {
text: "By Age",
submenu: [
{
text: "Most Recently Voted on",
click: "sortRelatedProposals('Most Recently Voted on')"
}, {
text: "Oldest Most Recent Vote",
click: "sortRelatedProposals('Oldest Most Recent Vote')"
}
]
}
];
return $scope.sortRelatedProposals = function(related_sort_by) {
$scope.relatedProposals.$get({
id: $scope.proposal.id,
related_sort_by: related_sort_by
});
return $scope.selectedSort = related_sort_by;
};
}
];
App.controller('ProposalListCtrl', ProposalListCtrl);
App.controller('ProposalShowCtrl', ProposalShowCtrl);
App.controller('RelatedProposalShowCtrl', RelatedProposalShowCtrl);
}).call(this);
//@ sourceMappingURL=proposals_controller.map(function() {
var RootCtrl;
RootCtrl = [
'$scope', '$rootScope', '$route', 'AlertService', '$location', '$modal', 'Auth', 'SessionService', 'SessionSettings', 'CurrentUserLoader', 'VotingService', function($scope, $rootScope, $route, AlertService, $location, $modal, Auth, SessionService, SessionSettings, CurrentUserLoader, VotingService) {
$rootScope.alertService = AlertService;
$rootScope.authService = Auth;
$rootScope.sessionSettings = SessionSettings;
$rootScope.votingService = VotingService;
CurrentUserLoader().then(function(current_user) {
$rootScope.currentUser = current_user;
if (($rootScope.currentUser.username != null) && $location.path() === '/') {
return $location.path('/proposals').search('filter', 'my');
}
});
window.fbAsyncInit = function() {
return FB.init({
appId: (function() {
switch ($location.host().substring(0, 3)) {
case 'loc':
return '449408378433518';
case 'sta':
return '122901591225638';
case 'www':
return '374325849312759';
}
})(),
cookie: true,
status: true,
xfbml: true
});
};
$scope.$on("event:loginRequired", function() {
return $scope.authService.signinFb($scope);
});
$scope.signinAuth = function() {
var modalInstance;
modalInstance = $modal.open({
templateUrl: 'user/_auth_intro_modal.html',
windowClass: 'dialog-sm'
});
return modalInstance.result.then(function(result) {
return $scope.authService.signinFb($scope);
});
};
$scope.userSettings = function() {
var modalInstance;
if (SessionSettings.openModals.userSettings === false) {
modalInstance = $modal.open({
templateUrl: 'user/_settings_modal.html',
controller: 'UserSettingsCtrl'
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.userSettings = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.userSettings = false;
});
}
};
$scope.signOut = function() {
SessionService.userOmniauth.$destroy();
$rootScope.currentUser = {};
$location.path('/').search('');
return AlertService.setInfo('You are signed out of Spokenvote.', $scope, 'main');
};
$scope.clearFilter = function(filter) {
$location.search(filter, null);
return $rootScope.sessionSettings.routeParams.user = null;
};
$scope.showProposal = function(proposal) {
return $location.path('/proposals/' + proposal.id).hash('navigationBar');
};
$scope.backtoTopics = function() {
$scope.sessionSettings.routeParams = $route.current.params;
return $location.path('/proposals').hash('prop' + $scope.sessionSettings.routeParams.proposalId);
};
$scope.newTopic = function() {
if ($scope.currentUser.id != null) {
return $scope.votingService["new"]($scope);
} else {
return $scope.authService.signinFb($scope).then(function() {
return $scope.votingService["new"]($scope);
});
}
};
$scope.getStarted = function() {
return $scope.votingService.wizard($scope);
};
return $rootScope.rootTips = {
newHub: "You may change the group to which you are directing this proposal by clicking here."
};
}
];
App.controller('RootCtrl', RootCtrl);
}).call(this);
//@ sourceMappingURL=root_controller.map(function() {
var SidebarCtrl;
SidebarCtrl = [
'$scope', '$routeParams', '$location', function($scope, $routeParams, $location) {
$scope.setFilter = function(filterSelected) {
$location.path('/proposals').search('filter', filterSelected);
return $scope.sessionSettings.routeParams.filter = filterSelected;
};
$scope.oneAtATime = true;
return $scope.isopen = false;
}
];
App.controller('SidebarCtrl', SidebarCtrl);
}).call(this);
//@ sourceMappingURL=sidebar_controller.map(function() {
var app;
app = angular.module("spokenvote");
app.controller('UserSettingsCtrl', function($scope, $modalInstance, CurrentUser) {
return $scope.saveUserSettings = function() {
$scope.alertService.clearAlerts();
return CurrentUser.save($scope.currentUser, function(response, status, headers, config) {
$scope.alertService.setSuccess($scope.currentUser.first_name + '\'s settings have been updated.', $scope, 'main');
return $modalInstance.close(response);
}, function(response, status, headers, config) {
$scope.alertService.setCtlResult('Sorry, ' + $scope.currentUser.first_name + ' your settings were not saved.', $scope, 'modal');
return $scope.alertService.setJson(response.data);
});
};
});
}).call(this);
//@ sourceMappingURL=user_controller.map(function() {
var DeleteProposalCtrl, EditProposalCtrl, ImroveCtrl, NewProposalCtrl, SupportCtrl;
SupportCtrl = [
'$scope', '$location', '$rootScope', '$modalInstance', 'Vote', function($scope, $location, $rootScope, $modalInstance, Vote) {
$scope.alertService.clearAlerts();
if ($rootScope.sessionSettings.newSupport.related != null) {
$scope.alertService.setCtlResult('We found support from you on another proposal. If you continue, your previous support will be moved here.', $scope, 'modal');
}
return $scope.saveSupport = function() {
var vote;
$scope.alertService.clearAlerts();
$rootScope.sessionSettings.newSupport.save.proposal_id = $rootScope.sessionSettings.newSupport.target.id;
return vote = Vote.save($scope.sessionSettings.newSupport.save, function(response, status, headers, config) {
$rootScope.$broadcast('event:votesChanged');
$scope.alertService.setSuccess('Your vote was created with the comment: \"' + response.comment + '\"', $scope, 'main');
$modalInstance.close(response);
return $location.path('/proposals/' + response.proposal_id).hash('prop' + $rootScope.sessionSettings.newSupport.save.proposal_id);
}, function(response, status, headers, config) {
$scope.alertService.setCtlResult('Sorry, your vote to support this proposal was not counted.', $scope, 'modal');
return $scope.alertService.setJson(response.data);
});
};
}
];
ImroveCtrl = [
'$scope', '$location', '$rootScope', '$modalInstance', 'Proposal', function($scope, $location, $rootScope, $modalInstance, Proposal) {
$scope.alertService.clearAlerts();
if ($scope.current_user_support === 'related_proposal') {
$scope.alertService.setCtlResult('We found support from you on another proposal. If you create a new, improved propsal your previous support will be moved here.', $scope, 'modal');
}
$scope.improvedProposal = {
statement: $scope.clicked_proposal.statement
};
return $scope.saveImprovement = function() {
var improvedProposal;
improvedProposal = {
proposal: {
parent_id: $scope.clicked_proposal.id,
statement: $scope.improvedProposal.statement,
votes_attributes: {
comment: $scope.improvedProposal.comment
}
}
};
$scope.alertService.clearAlerts();
return improvedProposal = Proposal.save(improvedProposal, function(response, status, headers, config) {
$location.path('/proposals/' + response.id);
$scope.alertService.setSuccess('Your improved proposal stating: \"' + response.statement + '\" was created.', $scope, 'main');
return $modalInstance.close(response);
}, function(response, status, headers, config) {
$scope.alertService.setCtlResult('Sorry, your improved proposal was not saved.', $scope, 'modal');
return $scope.alertService.setJson(response.data);
});
};
}
];
EditProposalCtrl = [
'$scope', '$location', '$rootScope', '$modalInstance', 'Proposal', function($scope, $location, $rootScope, $modalInstance, Proposal) {
$scope.clicked_proposal = $scope.clicked_proposal;
if ($scope.clicked_proposal.votes.length > 1) {
$scope.alertService.setCtlResult("We found support from other users on your proposal. You can no longer edit your proposal, but you can Improve it to get a similar result.", $scope);
}
$scope.editProposal = {
id: $scope.clicked_proposal.id,
proposal: {
statement: $scope.clicked_proposal.statement,
votes_attributes: {
comment: $scope.clicked_proposal.votes[0].comment,
id: $scope.clicked_proposal.votes[0].id
}
}
};
return $scope.saveEdit = function() {
$scope.alertService.clearAlerts();
return Proposal.update($scope.editProposal, function(response, status, headers, config) {
$rootScope.$broadcast('event:votesChanged');
$scope.alertService.setSuccess('Your proposal stating: \"' + response.statement + '\" has been saved.', $scope);
return $modalInstance.close(response);
}, function(response, status, headers, config) {
$scope.alertService.setCtlResult('Sorry, your improved proposal was not saved.', $scope);
return $scope.alertService.setJson(response.data);
});
};
}
];
DeleteProposalCtrl = [
'$scope', '$location', '$rootScope', '$modalInstance', 'Proposal', function($scope, $location, $rootScope, $modalInstance, Proposal) {
$scope.clicked_proposal = $scope.clicked_proposal;
if ($scope.clicked_proposal.votes.length > 1) {
$scope.alertService.setCtlResult("We found support from other users on your proposal. You can no longer delete your proposal, but you can Improve it if you'd like to make a different proposal.", $scope);
}
return $scope.deleteProposal = function() {
$scope.alertService.clearAlerts();
return Proposal["delete"]($scope.clicked_proposal, function(response, status, headers, config) {
$scope.alertService.setSuccess('Your proposal stating: \"' + $scope.clicked_proposal.statement + '\" was deleted.', $scope);
$location.path('/proposals').search('hub', $scope.clicked_proposal.hub_id);
return $modalInstance.close(response);
}, function(response, status, headers, config) {
$scope.alertService.setCtlResult('Sorry, your proposal could not be deleted.', $scope);
return $scope.alertService.setJson(response.data);
});
};
}
];
NewProposalCtrl = [
'$scope', '$modalInstance', function($scope, $modalInstance) {
return $scope.modalInstance = $modalInstance;
}
];
App.controller('SupportCtrl', SupportCtrl);
App.controller('ImroveCtrl', ImroveCtrl);
App.controller('EditProposalCtrl', EditProposalCtrl);
App.controller('DeleteProposalCtrl', DeleteProposalCtrl);
App.controller('NewProposalCtrl', NewProposalCtrl);
}).call(this);
//@ sourceMappingURL=votes_controller.map(function() {
var GetStartedCtrl;
GetStartedCtrl = [
'$scope', '$location', '$modalInstance', function($scope, $location, $modalInstance) {
$scope.alertService.clearAlerts();
$scope.modalInstance = $modalInstance;
$scope.sessionSettings.hub_attributes.id = null;
$scope.sessionSettings.actions.newProposalHub = null;
$scope.sessionSettings.actions.changeHub = true;
$scope.sessionSettings.actions.wizardToGroup = null;
return $scope.goToGroup = function(action) {
if ($scope.sessionSettings.hub_attributes.id != null) {
$location.path('/proposals').search('hub', $scope.sessionSettings.hub_attributes.id).hash('navigationBar');
$scope.sessionSettings.actions.hubFilter = $scope.sessionSettings.hub_attributes.group_name;
return $scope.sessionSettings.actions.wizardToGroup = action;
}
};
}
];
App.controller('GetStartedCtrl', GetStartedCtrl);
}).call(this);
//@ sourceMappingURL=wizard_controller.map(function() {
var alertBar;
alertBar = [
'$parse', '$rootScope', function($parse, $rootScope) {
return {
restrict: 'A',
templateUrl: 'shared/_request_response_partial.html',
link: function(scope, elem, attrs) {
var alertMessageAttr;
alertMessageAttr = attrs['alertmessageclear'];
return $rootScope.hideAlert = function() {
return $parse(alertMessageAttr).assign(scope, null);
};
}
};
}
];
App.Directives.directive('alertBar', alertBar);
}).call(this);
//@ sourceMappingURL=error_handling.map(function() {
var svGooglePlace;
svGooglePlace = function() {
return {
require: "ngModel",
link: function(scope, element, attrs, model) {
var autocomplete, defaultBounds, options;
model.$parsers.unshift(function() {
return model.$setValidity("location", false);
});
defaultBounds = new google.maps.LatLngBounds(new google.maps.LatLng(72.501722, -172.617188), new google.maps.LatLng(14.604847, -61.171875));
options = {
bounds: defaultBounds,
types: ['(regions)']
};
autocomplete = new google.maps.places.Autocomplete(element[0], options);
return google.maps.event.addListener(autocomplete, "place_changed", function() {
var location;
location = autocomplete.getPlace();
model.$setValidity("location", true);
return scope.$apply(function() {
scope.sessionSettings.hub_attributes.location_id = location.id;
return scope.sessionSettings.hub_attributes.formatted_location = location.formatted_address;
});
});
}
};
};
App.Directives.directive('svGooglePlace', svGooglePlace);
}).call(this);
//@ sourceMappingURL=geography.map(function() {
App.filter("capitalize", function() {
return function(input, scope) {
return input.substring(0, 1).toUpperCase() + input.replace(/_/, ' ').substring(1);
};
});
App.filter("slice", function() {
return function(arr, start, end) {
return arr.slice(start, end);
};
});
}).call(this);
//@ sourceMappingURL=formatting.map(function() {
var CurrentHubLoader, CurrentUser, CurrentUserLoader, Hub, MultiProposalLoader, Proposal, ProposalLoader, RelatedProposals, RelatedProposalsLoader, RelatedVoteInTree, RelatedVoteInTreeLoader, UserOmniauthResource, UserRegistrationResource, UserSessionResource, Vote;
CurrentUser = function($resource) {
return $resource('/currentuser');
};
Hub = function($resource) {
return $resource('/hubs/:id', {
id: '@id'
}, {
update: {
method: 'PUT'
}
});
};
Vote = function($resource) {
return $resource('/votes/:id', {
id: '@id'
}, {
update: {
method: 'PUT'
}
});
};
Proposal = function($resource) {
return $resource('/proposals/:id', {
id: '@id'
}, {
update: {
method: 'PUT'
}
});
};
RelatedProposals = function($resource) {
return $resource('/proposals/:id/related_proposals?related_sort_by=:related_sort_by', {
id: '@id',
related_sort_by: '@related_sort_by'
});
};
RelatedVoteInTree = function($resource) {
return $resource('/proposals/:id/related_vote_in_tree', {
id: '@id'
});
};
UserOmniauthResource = function($http) {
var UserOmniauth;
UserOmniauth = function(options) {
return angular.extend(this, options);
};
UserOmniauth.prototype.$save = function() {
return $http.post('/authentications', {
auth: this.auth
});
};
UserOmniauth.prototype.$destroy = function() {
return $http["delete"]("/users/logout");
};
return UserOmniauth;
};
UserSessionResource = function($http) {
var UserSession;
UserSession = function(options) {
return angular.extend(this, options);
};
return UserSession;
};
UserRegistrationResource = function($http) {
var UserRegistration;
UserRegistration = function(options) {
return angular.extend(this, options);
};
UserRegistration.prototype.$save = function() {
return $http.post("/users", {
user: {
name: this.name,
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation
}
});
};
return UserRegistration;
};
CurrentUserLoader = function(CurrentUser, $route, $q) {
return function() {
var delay;
delay = $q.defer();
CurrentUser.get({}, function(current_user) {
return delay.resolve(current_user);
}, function() {
return delay.reject('Unable to locate a current user ');
});
return delay.promise;
};
};
CurrentHubLoader = function(Hub, $route, $q) {
return function() {
var delay;
delay = $q.defer();
if ($route.current.params.hub) {
Hub.get({
id: $route.current.params.hub
}, function(hub) {
return delay.resolve(hub);
}, function() {
return delay.reject('Unable to locate a hub ');
});
} else {
delay.resolve(false);
}
return delay.promise;
};
};
ProposalLoader = function(Proposal, $route, $q) {
return function() {
var delay;
delay = $q.defer();
Proposal.get({
id: $route.current.params.proposalId
}, function(proposal) {
return delay.resolve(proposal);
}, function() {
return delay.reject('Unable to locate proposal ' + $route.current.params.proposalId);
});
return delay.promise;
};
};
MultiProposalLoader = [
'Proposal', '$route', '$q', function(Proposal, $route, $q) {
return function() {
var delay;
delay = $q.defer();
Proposal.query({
hub: $route.current.params.hub,
filter: $route.current.params.filter,
user: $route.current.params.user
}, function(proposals) {
return delay.resolve(proposals);
}, function() {
return delay.reject('Unable to locate proposals for hub' + $route.current.params.hub);
});
return delay.promise;
};
}
];
RelatedProposalsLoader = function(RelatedProposals, $route, $q) {
return function() {
var delay;
delay = $q.defer();
RelatedProposals.get({
id: $route.current.params.proposalId,
related_sort_by: $route.current.params.related_sort_by
}, function(related_proposals) {
return delay.resolve(related_proposals);
}, function() {
return delay.reject('Unable to locate related proposals ' + $route.current.params.proposalId);
});
return delay.promise;
};
};
RelatedVoteInTreeLoader = function(RelatedVoteInTree, $q) {
return function(clicked_proposal) {
var delay;
delay = $q.defer();
RelatedVoteInTree.get({
id: clicked_proposal.id
}, function(relatedVoteInTree) {
return delay.resolve(relatedVoteInTree);
}, function() {
return delay.reject('Unable to find any related votes in the tree for proposal: ' + clicked_proposal.id);
});
return delay.promise;
};
};
CurrentUser.$inject = ['$resource'];
Hub.$inject = ['$resource'];
Vote.$inject = ['$resource'];
Proposal.$inject = ['$resource'];
RelatedProposals.$inject = ['$resource'];
RelatedVoteInTree.$inject = ['$resource'];
UserOmniauthResource.$inject = ['$http'];
UserSessionResource.$inject = ['$http'];
UserRegistrationResource.$inject = ['$http'];
CurrentUserLoader.$inject = ['CurrentUser', '$route', '$q'];
CurrentHubLoader.$inject = ['Hub', '$route', '$q'];
ProposalLoader.$inject = ['Proposal', '$route', '$q'];
RelatedProposalsLoader.$inject = ['RelatedProposals', '$route', '$q'];
RelatedVoteInTreeLoader.$inject = ['RelatedVoteInTree', '$q'];
App.Services.factory('CurrentUser', CurrentUser);
App.Services.factory('Hub', Hub);
App.Services.factory('Vote', Vote);
App.Services.factory('Proposal', Proposal);
App.Services.factory('CurrentHubLoader', CurrentHubLoader);
App.Services.factory('RelatedProposals', RelatedProposals);
App.Services.factory('RelatedVoteInTree', RelatedVoteInTree);
App.Services.factory('UserOmniauthResource', UserOmniauthResource);
App.Services.factory('UserSessionResource', UserSessionResource);
App.Services.factory('UserRegistrationResource', UserRegistrationResource);
App.Services.factory('CurrentUserLoader', CurrentUserLoader);
App.Services.factory('ProposalLoader', ProposalLoader);
App.Services.factory('MultiProposalLoader', MultiProposalLoader);
App.Services.factory('RelatedProposalsLoader', RelatedProposalsLoader);
App.Services.factory('RelatedVoteInTreeLoader', RelatedVoteInTreeLoader);
}).call(this);
//@ sourceMappingURL=api.map(function() {
var Auth;
Auth = function($q, $rootScope, SessionSettings, SessionService, AlertService, CurrentUserLoader) {
var fbResolve, signinSv;
fbResolve = function(userInfo, error, deferredFb, scope) {
return $rootScope.$apply(function() {
AlertService.clearAlerts();
if (userInfo) {
SessionSettings.facebookUser.me = userInfo;
return signinSv(deferredFb, scope);
} else {
AlertService.setError('Error trying to sign you in to Facebook. Please try again', scope, 'main');
if (error != null) {
AlertService.setCtlResult(error.message, scope, 'main');
console.log(error);
}
return deferredFb.reject(error);
}
});
};
signinSv = function(deferred, scope) {
SessionService.userOmniauth.auth = {
provider: 'facebook',
uid: SessionSettings.facebookUser.me.id,
name: SessionSettings.facebookUser.me.name,
email: SessionSettings.facebookUser.me.email != null ? SessionSettings.facebookUser.me.email : void 0,
token: SessionSettings.facebookUser.auth.authResponse.accessToken,
expiresIn: SessionSettings.facebookUser.auth.authResponse.expiresIn
};
if (SessionService.signedOut) {
console.log("signed out");
return SessionService.userOmniauth.$save().success(function(sessionResponse) {
if (sessionResponse.success === true) {
$rootScope.authService.updateUserSession(scope).then(function() {
return deferred.resolve(sessionResponse);
});
}
if (sessionResponse.success === false) {
AlertService.setCtlResult('Sorry, we were not able to sign you in to Spokenvote.', scope, 'main');
return deferred.reject(sessionResponse);
}
});
}
};
return {
signinFb: function(scope) {
var deferredFb;
deferredFb = $q.defer();
FB.getLoginStatus(function(authResponse) {
if (authResponse.status === "connected") {
SessionSettings.facebookUser.auth = authResponse;
return FB.api("/me", function(userInfo) {
if (userInfo.error) {
return fbResolve(null, userInfo.error, deferredFb, scope);
} else {
return fbResolve(userInfo, null, deferredFb, scope);
}
});
} else {
return FB.login((function(authResponse) {
SessionSettings.facebookUser.auth = authResponse;
if (authResponse.status === "connected") {
return FB.api("/me", function(userInfo) {
if (userInfo.error) {
return fbResolve(null, userInfo.error, deferredFb, scope);
} else {
return fbResolve(userInfo, null, deferredFb, scope);
}
});
} else {
return fbResolve(null, authResponse, deferredFb, scope);
}
}), {
scope: 'email,user_likes'
});
}
});
return deferredFb.promise;
},
updateUserSession: function(scope) {
return CurrentUserLoader().then(function(current_user) {
$rootScope.currentUser = current_user;
$rootScope.alertService.clearAlerts;
$rootScope.alertService.setInfo('You are signed in to Spokenvote!', scope, 'main');
$rootScope.$broadcast('event:votesChanged');
return CurrentUserLoader();
});
}
};
};
Auth.$inject = ['$q', '$rootScope', 'SessionSettings', 'SessionService', 'AlertService', 'CurrentUserLoader'];
App.Services.factory('Auth', Auth);
}).call(this);
//@ sourceMappingURL=authentication.map(function() {
var AlertService, SessionService, SessionSettings, SpokenvoteCookies, errorHttpInterceptor;
SessionService = function($cookieStore, UserSessionResource, UserRegistrationResource, UserOmniauthResource) {
return {
currentUser: $cookieStore.get('_spokenvote_session'),
signedIn: !!$cookieStore.get('_spokenvote_session'),
signedOut: !this.signedIn,
userSession: new UserSessionResource({
email: $cookieStore.get('spokenvote_email'),
password: null,
remember_me: true
}),
userOmniauth: new UserOmniauthResource({
auth: null
}),
userRegistration: new UserRegistrationResource({
name: null,
email: $cookieStore.get('spokenvote_email'),
password: null,
password_confirmation: null
})
};
};
AlertService = function($timeout) {
return {
callingScope: null,
alertMessage: null,
jsonResponse: null,
jsonErrors: null,
alertClass: null,
alertDestination: null,
cltActionResult: null,
setSuccess: function(msg, scope, dest) {
this.alertMessage = msg;
this.alertDestination = dest;
this.alertClass = 'alert-success';
if (scope != null) {
return $timeout((function() {
return scope.hideAlert();
}), 7000);
}
},
setInfo: function(msg, scope, dest) {
this.alertMessage = msg;
this.alertDestination = dest;
this.alertClass = 'alert-info';
if (scope != null) {
return $timeout((function() {
return scope.hideAlert();
}), 7000);
}
},
setError: function(msg, scope, dest) {
this.alertMessage = msg;
this.alertDestination = dest;
this.alertClass = 'alert-danger';
if (scope != null) {
return $timeout((function() {
return scope.hideAlert();
}), 7000);
}
},
setCtlResult: function(result, scope, dest) {
this.cltActionResult = result;
this.alertDestination = dest;
return this.alertClass = 'alert-danger';
},
setJson: function(json) {
this.jsonResponse = json;
if (json > ' ') {
return this.jsonErrors = json;
}
},
setCallingScope: function(scope) {
return this.callingScope = scope;
},
setClass: function(alertclass) {
return this.alertClass = alertclass;
},
clearAlerts: function() {
this.callingScope = null;
this.alertMessage = null;
this.jsonResponse = null;
this.jsonErrors = null;
this.alertClass = null;
return this.cltActionResult = null;
}
};
};
errorHttpInterceptor = function($q, $location, $rootScope, AlertService) {
return function(promise) {
return promise.then((function(response) {
return response;
}), function(response) {
if (response.status === 406) {
AlertService.setError("Please sign in to continue.");
$rootScope.$broadcast("event:loginRequired");
} else {
if (response.status >= 400 && response.status < 500) {
AlertService.setError("The server was unable to process your request.");
}
}
return $q.reject(response);
});
};
};
SessionSettings = function() {
return {
facebookUser: {
auth: {},
me: {}
},
actions: {
hubFilter: 'All Groups',
userFilter: null,
changeHub: false,
newProposalHub: null,
searchTerm: null,
wizardToGroup: null,
selectHub: false,
offcanvas: false,
detailPage: false
},
openModals: {
signIn: false,
register: false,
register: false,
userSettings: false,
supportProposal: false,
improveProposal: false,
newProposal: false,
editProposal: false,
deleteProposal: false,
getStarted: false
},
searchedHub: {},
routeParams: {},
hub_attributes: {},
newProposal: {},
newSupport: {
target: {},
related: {},
save: {}
},
lastLocation: {
location_id: null,
formatted_location: null
},
socialSharing: {
twitterRootUrl: 'http://twitter.com/home?status=',
facebookRootUrl: 'http://www.facebook.com/sharer.php?u=',
googleRootUrl: 'https://plus.google.com/share?url='
},
spokenvote_attributes: {
defaultGravatar: 'http://www.spokenvote.com/' + 'assets/icons/sv-30.png',
googleOauth2Config: {
client_id: '390524033908-kqnb56kof2vfr4gssi2q84nth2n981g5',
scope: ['https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile']
}
}
};
};
SpokenvoteCookies = function($cookies) {
$cookies.SpokenvoteSession = "Setting a value";
return {
sessionCookie: $cookies.SpokenvoteSession
};
};
SessionService.$inject = ['$cookieStore', 'UserSessionResource', 'UserRegistrationResource', 'UserOmniauthResource'];
AlertService.$inject = ['$timeout'];
errorHttpInterceptor.$inject = ['$q', '$location', '$rootScope', 'AlertService'];
SpokenvoteCookies.$inject = ['$cookies'];
App.Services.factory('SessionService', SessionService);
App.Services.factory('AlertService', AlertService);
App.Services.factory('errorHttpInterceptor', errorHttpInterceptor);
App.Services.factory('SpokenvoteCookies', SpokenvoteCookies);
App.Services.factory('SessionSettings', SessionSettings);
}).call(this);
//@ sourceMappingURL=session.map(function() {
var VotingService;
VotingService = [
'$rootScope', '$location', '$modal', 'SessionSettings', 'RelatedVoteInTreeLoader', 'Proposal', function($rootScope, $location, $modal, SessionSettings, RelatedVoteInTreeLoader, Proposal) {
return {
support: function(clicked_proposal) {
$rootScope.sessionSettings.newSupport.target = clicked_proposal;
$rootScope.sessionSettings.newSupport.related = null;
$rootScope.alertService.clearAlerts();
if ($rootScope.currentUser.id == null) {
return $rootScope.alertService.setInfo('To support proposals you need to sign in.', scope, 'main');
} else {
return RelatedVoteInTreeLoader(clicked_proposal).then(function(relatedSupport) {
var modalInstance;
if (relatedSupport.id != null) {
$rootScope.sessionSettings.newSupport.related = relatedSupport;
if (relatedSupport.proposal.id === clicked_proposal.id) {
$rootScope.alertService.setInfo('Good news, it looks as if you have already supported this proposal. Further editing is not allowed at this time.', $rootScope, 'main');
return;
}
}
if (SessionSettings.openModals.supportProposal === false) {
modalInstance = $modal.open({
templateUrl: 'proposals/_support_modal.html',
controller: 'SupportCtrl'
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.supportProposal = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.supportProposal = false;
});
}
});
}
},
improve: function(scope, clicked_proposal) {
scope.clicked_proposal = clicked_proposal;
scope.current_user_support = null;
$rootScope.alertService.clearAlerts();
if (scope.currentUser.id == null) {
return $rootScope.alertService.setInfo('To improve proposals you need to sign in.', scope, 'main');
} else {
return RelatedVoteInTreeLoader(clicked_proposal).then(function(relatedSupport) {
var modalInstance;
if (relatedSupport.id != null) {
scope.current_user_support = 'related_proposal';
}
if (SessionSettings.openModals.improveProposal === false) {
modalInstance = $modal.open({
templateUrl: 'proposals/_improve_proposal_modal.html',
controller: 'ImroveCtrl',
scope: scope
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.improveProposal = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.improveProposal = false;
});
}
});
}
},
edit: function(scope, clicked_proposal) {
var modalInstance;
scope.clicked_proposal = clicked_proposal;
if (scope.currentUser.id == null) {
return $rootScope.alertService.setInfo('To proceed you need to sign in.', scope, 'main');
} else {
if (SessionSettings.openModals.editProposal === false) {
modalInstance = $modal.open({
templateUrl: 'proposals/_edit_proposal_modal.html',
controller: 'EditProposalCtrl',
scope: scope
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.editProposal = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.editProposal = false;
});
}
}
},
"delete": function(scope, clicked_proposal) {
var modalInstance;
scope.clicked_proposal = clicked_proposal;
if (scope.currentUser.id == null) {
return $rootScope.alertService.setInfo('To proceed you need to sign in.', scope, 'main');
} else {
if (SessionSettings.openModals.deleteProposal === false) {
modalInstance = $modal.open({
templateUrl: 'proposals/_delete_proposal_modal.html',
controller: 'DeleteProposalCtrl',
scope: scope
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.deleteProposal = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.deleteProposal = false;
});
}
}
},
"new": function(scope) {
var modalInstance;
$rootScope.alertService.clearAlerts();
if (SessionSettings.hub_attributes.id != null) {
SessionSettings.actions.changeHub = false;
} else {
SessionSettings.actions.changeHub = true;
SessionSettings.actions.searchTerm = null;
}
if ($rootScope.currentUser.id == null) {
return $rootScope.alertService.setInfo('To create proposals you need to sign in.', $rootScope, 'main');
} else {
if (SessionSettings.openModals.newProposal === false) {
modalInstance = $modal.open({
templateUrl: 'proposals/_new_proposal_modal.html',
controller: 'NewProposalCtrl'
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.newProposal = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.newProposal = false;
});
}
}
},
wizard: function(scope) {
var modalInstance;
if (SessionSettings.openModals.getStarted === false) {
modalInstance = $modal.open({
templateUrl: 'shared/_get_started_modal.html',
controller: 'GetStartedCtrl'
});
modalInstance.opened.then(function() {
return SessionSettings.openModals.getStarted = true;
});
return modalInstance.result["finally"](function() {
return SessionSettings.openModals.getStarted = false;
});
}
},
changeHub: function(request) {
if (request = true && SessionSettings.actions.changeHub !== 'new') {
SessionSettings.actions.newProposalHub = null;
return SessionSettings.actions.changeHub = !SessionSettings.actions.changeHub;
}
},
saveNewProposal: function($modalInstance) {
var newProposal;
if (SessionSettings.hub_attributes.id == null) {
SessionSettings.hub_attributes.group_name = SessionSettings.actions.searchTerm;
}
newProposal = {
proposal: {
statement: SessionSettings.newProposal.statement,
votes_attributes: {
comment: SessionSettings.newProposal.comment
},
hub_id: SessionSettings.hub_attributes.id,
hub_attributes: SessionSettings.hub_attributes
}
};
$rootScope.alertService.clearAlerts();
return Proposal.save(newProposal, function(response, status, headers, config) {
$rootScope.$broadcast('event:proposalsChanged');
$rootScope.alertService.setSuccess('Your new proposal stating: \"' + response.statement + '\" was created.', $rootScope, 'main');
$location.path('/proposals/' + response.id).search('hub', response.hub_id).search('filter', 'my').hash('navigationBar');
$modalInstance.close(response);
return SessionSettings.actions.offcanvas = false;
}, function(response, status, headers, config) {
$rootScope.alertService.setCtlResult('Sorry, your new proposal was not saved.', $rootScope, 'modal');
return $rootScope.alertService.setJson(response.data);
});
}
};
}
];
App.Services.factory('VotingService', VotingService);
}).call(this);
//@ sourceMappingURL=voting.map/*!
* angular-loading-bar v0.5.0
* https://chieffancypants.github.io/angular-loading-bar
* Copyright (c) 2014 Wes Cruver
* License: MIT
*/
/*
* angular-loading-bar
*
* intercepts XHR requests and creates a loading bar.
* Based on the excellent nprogress work by rstacruz (more info in readme)
*
* (c) 2013 Wes Cruver
* License: MIT
*/
(function() {
'use strict';
// Alias the loading bar for various backwards compatibilities since the project has matured:
angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']);
angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']);
/**
* loadingBarInterceptor service
*
* Registers itself as an Angular interceptor and listens for XHR requests.
*/
angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
.config(['$httpProvider', function ($httpProvider) {
var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) {
/**
* The total number of requests made
*/
var reqsTotal = 0;
/**
* The number of requests completed (either successfully or not)
*/
var reqsCompleted = 0;
/**
* The amount of time spent fetching before showing the loading bar
*/
var latencyThreshold = cfpLoadingBar.latencyThreshold;
/**
* $timeout handle for latencyThreshold
*/
var startTimeout;
/**
* calls cfpLoadingBar.complete() which removes the
* loading bar from the DOM.
*/
function setComplete() {
$timeout.cancel(startTimeout);
cfpLoadingBar.complete();
reqsCompleted = 0;
reqsTotal = 0;
}
/**
* Determine if the response has already been cached
* @param {Object} config the config option from the request
* @return {Boolean} retrns true if cached, otherwise false
*/
function isCached(config) {
var cache;
var defaults = $httpProvider.defaults;
if (config.method !== 'GET' || config.cache === false) {
config.cached = false;
return false;
}
if (config.cache === true && defaults.cache === undefined) {
cache = $cacheFactory.get('$http');
} else if (defaults.cache !== undefined) {
cache = defaults.cache;
} else {
cache = config.cache;
}
var cached = cache !== undefined ?
cache.get(config.url) !== undefined : false;
if (config.cached !== undefined && cached !== config.cached) {
return config.cached;
}
config.cached = cached;
return cached;
}
return {
'request': function(config) {
// Check to make sure this request hasn't already been cached and that
// the requester didn't explicitly ask us to ignore this request:
if (!config.ignoreLoadingBar && !isCached(config)) {
$rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url});
if (reqsTotal === 0) {
startTimeout = $timeout(function() {
cfpLoadingBar.start();
}, latencyThreshold);
}
reqsTotal++;
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
return config;
},
'response': function(response) {
if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
reqsCompleted++;
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url});
if (reqsCompleted >= reqsTotal) {
setComplete();
} else {
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
}
return response;
},
'responseError': function(rejection) {
if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
reqsCompleted++;
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url});
if (reqsCompleted >= reqsTotal) {
setComplete();
} else {
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
}
return $q.reject(rejection);
}
};
}];
$httpProvider.interceptors.push(interceptor);
}]);
/**
* Loading Bar
*
* This service handles adding and removing the actual element in the DOM.
* Generally, best practices for DOM manipulation is to take place in a
* directive, but because the element itself is injected in the DOM only upon
* XHR requests, and it's likely needed on every view, the best option is to
* use a service.
*/
angular.module('cfp.loadingBar', [])
.provider('cfpLoadingBar', function() {
this.includeSpinner = true;
this.includeBar = true;
this.latencyThreshold = 100;
this.startSize = 0.02;
this.parentSelector = 'body';
this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
this.$get = ['$document', '$timeout', '$animate', '$rootScope', function ($document, $timeout, $animate, $rootScope) {
var $parentSelector = this.parentSelector,
loadingBarContainer = angular.element('<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>'),
loadingBar = loadingBarContainer.find('div').eq(0),
spinner = angular.element(this.spinnerTemplate);
var incTimeout,
completeTimeout,
started = false,
status = 0;
var includeSpinner = this.includeSpinner;
var includeBar = this.includeBar;
var startSize = this.startSize;
/**
* Inserts the loading bar element into the dom, and sets it to 2%
*/
function _start() {
var $parent = $document.find($parentSelector);
$timeout.cancel(completeTimeout);
// do not continually broadcast the started event:
if (started) {
return;
}
$rootScope.$broadcast('cfpLoadingBar:started');
started = true;
if (includeBar) {
$animate.enter(loadingBarContainer, $parent);
}
if (includeSpinner) {
$animate.enter(spinner, $parent);
}
_set(startSize);
}
/**
* Set the loading bar's width to a certain percent.
*
* @param n any value between 0 and 1
*/
function _set(n) {
if (!started) {
return;
}
var pct = (n * 100) + '%';
loadingBar.css('width', pct);
status = n;
// increment loadingbar to give the illusion that there is always
// progress but make sure to cancel the previous timeouts so we don't
// have multiple incs running at the same time.
$timeout.cancel(incTimeout);
incTimeout = $timeout(function() {
_inc();
}, 250);
}
/**
* Increments the loading bar by a random amount
* but slows down as it progresses
*/
function _inc() {
if (_status() >= 1) {
return;
}
var rnd = 0;
// TODO: do this mathmatically instead of through conditions
var stat = _status();
if (stat >= 0 && stat < 0.25) {
// Start out between 3 - 6% increments
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
} else if (stat >= 0.25 && stat < 0.65) {
// increment between 0 - 3%
rnd = (Math.random() * 3) / 100;
} else if (stat >= 0.65 && stat < 0.9) {
// increment between 0 - 2%
rnd = (Math.random() * 2) / 100;
} else if (stat >= 0.9 && stat < 0.99) {
// finally, increment it .5 %
rnd = 0.005;
} else {
// after 99%, don't increment:
rnd = 0;
}
var pct = _status() + rnd;
_set(pct);
}
function _status() {
return status;
}
function _complete() {
$rootScope.$broadcast('cfpLoadingBar:completed');
_set(1);
$timeout.cancel(completeTimeout);
// Attempt to aggregate any start/complete calls within 500ms:
completeTimeout = $timeout(function() {
$animate.leave(loadingBarContainer, function() {
status = 0;
started = false;
});
$animate.leave(spinner);
}, 500);
}
return {
start : _start,
set : _set,
status : _status,
inc : _inc,
complete : _complete,
includeSpinner : this.includeSpinner,
latencyThreshold : this.latencyThreshold,
parentSelector : this.parentSelector,
startSize : this.startSize
};
}]; //
}); // wtf javascript. srsly
})(); //
;
(function() {
window.addToHomeConfig = {
startDelay: 2000,
lifespan: 30000,
expire: 720,
message: 'To install the Spokenvote App on your %device: tap %icon and then <br><strong>Add to Home Screen.</strong>',
touchIcon: true
};
}).call(this);
//@ sourceMappingURL=add2homeConfig.map/*!
* Add to Homescreen v2.0.11 ~ Copyright (c) 2013 Matteo Spinelli, http://cubiq.org
* Released under MIT license, http://cubiq.org/license
*/
var addToHome = (function (w) {
var nav = w.navigator,
isIDevice = 'platform' in nav && (/iphone|ipod|ipad/gi).test(nav.platform),
isIPad,
isRetina,
isSafari,
isStandalone,
OSVersion,
startX = 0,
startY = 0,
lastVisit = 0,
isExpired,
isSessionActive,
isReturningVisitor,
balloon,
overrideChecks,
positionInterval,
closeTimeout,
options = {
autostart: true, // Automatically open the balloon
returningVisitor: false, // Show the balloon to returning visitors only (setting this to true is highly recommended)
animationIn: 'drop', // drop || bubble || fade
animationOut: 'fade', // drop || bubble || fade
startDelay: 2000, // 2 seconds from page load before the balloon appears
lifespan: 15000, // 15 seconds before it is automatically destroyed
bottomOffset: 14, // Distance of the balloon from bottom
expire: 0, // Minutes to wait before showing the popup again (0 = always displayed)
message: '', // Customize your message or force a language ('' = automatic)
touchIcon: false, // Display the touch icon
arrow: true, // Display the balloon arrow
hookOnLoad: true, // Should we hook to onload event? (really advanced usage)
closeButton: true, // Let the user close the balloon
iterations: 100 // Internal/debug use
},
intl = {
ar: '<span dir="rtl">قم بتثبيت هذا التطبيق على <span dir="ltr">%device:</span>انقر<span dir="ltr">%icon</span> ،<strong>ثم اضفه الى الشاشة الرئيسية.</strong></span>',
ca_es: 'Per instal·lar aquesta aplicació al vostre %device premeu %icon i llavors <strong>Afegir a pantalla d\'inici</strong>.',
cs_cz: 'Pro instalaci aplikace na Váš %device, stiskněte %icon a v nabídce <strong>Přidat na plochu</strong>.',
da_dk: 'Tilføj denne side til din %device: tryk på %icon og derefter <strong>Føj til hjemmeskærm</strong>.',
de_de: 'Installieren Sie diese App auf Ihrem %device: %icon antippen und dann <strong>Zum Home-Bildschirm</strong>.',
el_gr: 'Εγκαταστήσετε αυτήν την Εφαρμογή στήν συσκευή σας %device: %icon μετά πατάτε <strong>Προσθήκη σε Αφετηρία</strong>.',
en_us: 'Install this web app on your %device: tap %icon and then <strong>Add to Home Screen</strong>.',
es_es: 'Para instalar esta app en su %device, pulse %icon y seleccione <strong>Añadir a pantalla de inicio</strong>.',
fi_fi: 'Asenna tämä web-sovellus laitteeseesi %device: paina %icon ja sen jälkeen valitse <strong>Lisää Koti-valikkoon</strong>.',
fr_fr: 'Ajoutez cette application sur votre %device en cliquant sur %icon, puis <strong>Ajouter à l\'écran d\'accueil</strong>.',
he_il: '<span dir="rtl">התקן אפליקציה זו על ה-%device שלך: הקש %icon ואז <strong>הוסף למסך הבית</strong>.</span>',
hr_hr: 'Instaliraj ovu aplikaciju na svoj %device: klikni na %icon i odaberi <strong>Dodaj u početni zaslon</strong>.',
hu_hu: 'Telepítse ezt a web-alkalmazást az Ön %device-jára: nyomjon a %icon-ra majd a <strong>Főképernyőhöz adás</strong> gombra.',
it_it: 'Installa questa applicazione sul tuo %device: premi su %icon e poi <strong>Aggiungi a Home</strong>.',
ja_jp: 'このウェブアプリをあなたの%deviceにインストールするには%iconをタップして<strong>ãƒ›ãƒ¼ãƒ ç”»é¢ã«è¿½åŠ </strong>を選んでください。',
ko_kr: '%device에 웹앱을 ì„¤ì¹˜í•˜ë ¤ë©´ %icon을 터치 후 "홈화면에 추가"를 ì„ íƒí•˜ì„¸ìš”',
nb_no: 'Installer denne appen på din %device: trykk på %icon og deretter <strong>Legg til på Hjem-skjerm</strong>',
nl_nl: 'Installeer deze webapp op uw %device: tik %icon en dan <strong>Voeg toe aan beginscherm</strong>.',
pl_pl: 'Aby zainstalować tę aplikacje na %device: naciśnij %icon a następnie <strong>Dodaj jako ikonę</strong>.',
pt_br: 'Instale este aplicativo em seu %device: aperte %icon e selecione <strong>Adicionar à Tela Inicio</strong>.',
pt_pt: 'Para instalar esta aplicação no seu %device, prima o %icon e depois em <strong>Adicionar ao ecrã principal</strong>.',
ru_ru: 'Установите это веб-приложение на ваш %device: нажмите %icon, затем <strong>Добавить в «Домой»</strong>.',
sv_se: 'Lägg till denna webbapplikation på din %device: tryck på %icon och därefter <strong>Lägg till på hemskärmen</strong>.',
th_th: 'ติดตั้งเว็บแอพฯ นี้บน %device ของคุณ: แตะ %icon และ <strong>เพิ่มที่หน้าจอโฮม</strong>',
tr_tr: 'Bu uygulamayı %device\'a eklemek için %icon simgesine sonrasında <strong>Ana Ekrana Ekle</strong> düğmesine basın.',
uk_ua: 'Встановіть цей веб сайт на Ваш %device: натисніть %icon, а потім <strong>На початковий екран</strong>.',
zh_cn: '您可以将此应用程式安装到您的 %device 上。请按 %icon 然后点选<strong>æ·»åŠ è‡³ä¸»å±å¹•</strong>。',
zh_tw: '您可以將此應用程式安裝到您的 %device 上。請按 %icon 然後點選<strong>åŠ å…¥ä¸»ç•«é¢èž¢å¹•</strong>。'
};
function init () {
// Preliminary check, all further checks are performed on iDevices only
if ( !isIDevice ) return;
var now = Date.now(),
i;
// Merge local with global options
if ( w.addToHomeConfig ) {
for ( i in w.addToHomeConfig ) {
options[i] = w.addToHomeConfig[i];
}
}
if ( !options.autostart ) options.hookOnLoad = false;
isIPad = (/ipad/gi).test(nav.platform);
isRetina = w.devicePixelRatio && w.devicePixelRatio > 1;
isSafari = (/Safari/i).test(nav.appVersion) && !(/CriOS/i).test(nav.appVersion);
isStandalone = nav.standalone;
OSVersion = nav.appVersion.match(/OS (\d+_\d+)/i);
OSVersion = OSVersion && OSVersion[1] ? +OSVersion[1].replace('_', '.') : 0;
lastVisit = +w.localStorage.getItem('addToHome');
isSessionActive = w.sessionStorage.getItem('addToHomeSession');
isReturningVisitor = options.returningVisitor ? lastVisit && lastVisit + 28*24*60*60*1000 > now : true;
if ( !lastVisit ) lastVisit = now;
// If it is expired we need to reissue a new balloon
isExpired = isReturningVisitor && lastVisit <= now;
if ( options.hookOnLoad ) w.addEventListener('load', loaded, false);
else if ( !options.hookOnLoad && options.autostart ) loaded();
}
function loaded () {
w.removeEventListener('load', loaded, false);
if ( !isReturningVisitor ) w.localStorage.setItem('addToHome', Date.now());
else if ( options.expire && isExpired ) w.localStorage.setItem('addToHome', Date.now() + options.expire * 60000);
if ( !overrideChecks && ( !isSafari || !isExpired || isSessionActive || isStandalone || !isReturningVisitor ) ) return;
var touchIcon = '',
platform = nav.platform.split(' ')[0],
language = nav.language.replace('-', '_');
balloon = document.createElement('div');
balloon.id = 'addToHomeScreen';
balloon.style.cssText += 'left:-9999px;-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0;-webkit-transform:translate3d(0,0,0);position:' + (OSVersion < 5 ? 'absolute' : 'fixed');
// Localize message
if ( options.message in intl ) { // You may force a language despite the user's locale
language = options.message;
options.message = '';
}
if ( options.message === '' ) { // We look for a suitable language (defaulted to en_us)
options.message = language in intl ? intl[language] : intl['en_us'];
}
if ( options.touchIcon ) {
touchIcon = isRetina ?
document.querySelector('head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon]') :
document.querySelector('head link[rel^=apple-touch-icon][sizes="57x57"],head link[rel^=apple-touch-icon]');
if ( touchIcon ) {
touchIcon = '<span style="background-image:url(' + touchIcon.href + ')" class="addToHomeTouchIcon"></span>';
}
}
balloon.className = (OSVersion >=7 ? 'addToHomeIOS7 ' : '') + (isIPad ? 'addToHomeIpad' : 'addToHomeIphone') + (touchIcon ? ' addToHomeWide' : '');
balloon.innerHTML = touchIcon +
options.message.replace('%device', platform).replace('%icon', OSVersion >= 4.2 ? '<span class="addToHomeShare"></span>' : '<span class="addToHomePlus">+</span>') +
(options.arrow ? '<span class="addToHomeArrow"' + (OSVersion >= 7 && isIPad && touchIcon ? ' style="margin-left:-32px"' : '') + '></span>' : '') +
(options.closeButton ? '<span class="addToHomeClose">\u00D7</span>' : '');
document.body.appendChild(balloon);
// Add the close action
if ( options.closeButton ) balloon.addEventListener('click', clicked, false);
if ( !isIPad && OSVersion >= 6 ) window.addEventListener('orientationchange', orientationCheck, false);
setTimeout(show, options.startDelay);
}
function show () {
var duration,
iPadXShift = 208;
// Set the initial position
if ( isIPad ) {
if ( OSVersion < 5 ) {
startY = w.scrollY;
startX = w.scrollX;
} else if ( OSVersion < 6 ) {
iPadXShift = 160;
} else if ( OSVersion >= 7 ) {
iPadXShift = 143;
}
balloon.style.top = startY + options.bottomOffset + 'px';
balloon.style.left = Math.max(startX + iPadXShift - Math.round(balloon.offsetWidth / 2), 9) + 'px';
switch ( options.animationIn ) {
case 'drop':
duration = '0.6s';
balloon.style.webkitTransform = 'translate3d(0,' + -(w.scrollY + options.bottomOffset + balloon.offsetHeight) + 'px,0)';
break;
case 'bubble':
duration = '0.6s';
balloon.style.opacity = '0';
balloon.style.webkitTransform = 'translate3d(0,' + (startY + 50) + 'px,0)';
break;
default:
duration = '1s';
balloon.style.opacity = '0';
}
} else {
startY = w.innerHeight + w.scrollY;
if ( OSVersion < 5 ) {
startX = Math.round((w.innerWidth - balloon.offsetWidth) / 2) + w.scrollX;
balloon.style.left = startX + 'px';
balloon.style.top = startY - balloon.offsetHeight - options.bottomOffset + 'px';
} else {
balloon.style.left = '50%';
balloon.style.marginLeft = -Math.round(balloon.offsetWidth / 2) - ( w.orientation%180 && OSVersion >= 6 && OSVersion < 7 ? 40 : 0 ) + 'px';
balloon.style.bottom = options.bottomOffset + 'px';
}
switch (options.animationIn) {
case 'drop':
duration = '1s';
balloon.style.webkitTransform = 'translate3d(0,' + -(startY + options.bottomOffset) + 'px,0)';
break;
case 'bubble':
duration = '0.6s';
balloon.style.webkitTransform = 'translate3d(0,' + (balloon.offsetHeight + options.bottomOffset + 50) + 'px,0)';
break;
default:
duration = '1s';
balloon.style.opacity = '0';
}
}
balloon.offsetHeight; // repaint trick
balloon.style.webkitTransitionDuration = duration;
balloon.style.opacity = '1';
balloon.style.webkitTransform = 'translate3d(0,0,0)';
balloon.addEventListener('webkitTransitionEnd', transitionEnd, false);
closeTimeout = setTimeout(close, options.lifespan);
}
function manualShow (override) {
if ( !isIDevice || balloon ) return;
overrideChecks = override;
loaded();
}
function close () {
clearInterval( positionInterval );
clearTimeout( closeTimeout );
closeTimeout = null;
// check if the popup is displayed and prevent errors
if ( !balloon ) return;
var posY = 0,
posX = 0,
opacity = '1',
duration = '0';
if ( options.closeButton ) balloon.removeEventListener('click', clicked, false);
if ( !isIPad && OSVersion >= 6 ) window.removeEventListener('orientationchange', orientationCheck, false);
if ( OSVersion < 5 ) {
posY = isIPad ? w.scrollY - startY : w.scrollY + w.innerHeight - startY;
posX = isIPad ? w.scrollX - startX : w.scrollX + Math.round((w.innerWidth - balloon.offsetWidth)/2) - startX;
}
balloon.style.webkitTransitionProperty = '-webkit-transform,opacity';
switch ( options.animationOut ) {
case 'drop':
if ( isIPad ) {
duration = '0.4s';
opacity = '0';
posY += 50;
} else {
duration = '0.6s';
posY += balloon.offsetHeight + options.bottomOffset + 50;
}
break;
case 'bubble':
if ( isIPad ) {
duration = '0.8s';
posY -= balloon.offsetHeight + options.bottomOffset + 50;
} else {
duration = '0.4s';
opacity = '0';
posY -= 50;
}
break;
default:
duration = '0.8s';
opacity = '0';
}
balloon.addEventListener('webkitTransitionEnd', transitionEnd, false);
balloon.style.opacity = opacity;
balloon.style.webkitTransitionDuration = duration;
balloon.style.webkitTransform = 'translate3d(' + posX + 'px,' + posY + 'px,0)';
}
function clicked () {
w.sessionStorage.setItem('addToHomeSession', '1');
isSessionActive = true;
close();
}
function transitionEnd () {
balloon.removeEventListener('webkitTransitionEnd', transitionEnd, false);
balloon.style.webkitTransitionProperty = '-webkit-transform';
balloon.style.webkitTransitionDuration = '0.2s';
// We reached the end!
if ( !closeTimeout ) {
balloon.parentNode.removeChild(balloon);
balloon = null;
return;
}
// On iOS 4 we start checking the element position
if ( OSVersion < 5 && closeTimeout ) positionInterval = setInterval(setPosition, options.iterations);
}
function setPosition () {
var matrix = new WebKitCSSMatrix(w.getComputedStyle(balloon, null).webkitTransform),
posY = isIPad ? w.scrollY - startY : w.scrollY + w.innerHeight - startY,
posX = isIPad ? w.scrollX - startX : w.scrollX + Math.round((w.innerWidth - balloon.offsetWidth) / 2) - startX;
// Screen didn't move
if ( posY == matrix.m42 && posX == matrix.m41 ) return;
balloon.style.webkitTransform = 'translate3d(' + posX + 'px,' + posY + 'px,0)';
}
// Clear local and session storages (this is useful primarily in development)
function reset () {
w.localStorage.removeItem('addToHome');
w.sessionStorage.removeItem('addToHomeSession');
}
function orientationCheck () {
balloon.style.marginLeft = -Math.round(balloon.offsetWidth / 2) - ( w.orientation%180 && OSVersion >= 6 && OSVersion < 7 ? 40 : 0 ) + 'px';
}
// Bootstrap!
init();
return {
show: manualShow,
close: close,
reset: reset
};
})(window);
// require jquery_ujs # Just jquery, right?
// require jquery_nested_form # Not quite sure if we need or don't need.
// require bootstrap/affix
// require bootstrap/alert
// require bootstrap/button
// require bootstrap/carousel
// require bootstrap/dropdown
// require bootstrap/scrollspy
// require bootstrap/modal
// require bootstrap/tooltip
// require bootstrap/popover
// require bootstrap/tab
// require holder #Don't believe this is in use
// require viewport.min #Don't believe we're calling this any more
// require ui-bootstrap-tpls-0.4.0
// require angular-strap
// require angular-cookies
;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment