');
+
+ return this.each(function () {
+ //if a source is specified
+ if (settings.source === "") {
+ if (window.console) {
+ window.console.log("Please specify a source first - boxRefresh()");
+ }
+ return;
+ }
+ //the box
+ var box = $(this);
+ //the button
+ var rBtn = box.find(settings.trigger).first();
+
+ //On trigger click
+ rBtn.on('click', function (e) {
+ e.preventDefault();
+ //Add loading overlay
+ start(box);
+
+ //Perform ajax call
+ box.find(".box-body").load(settings.source, function () {
+ done(box);
+ });
+ });
+ });
+
+ function start(box) {
+ //Add overlay and loading img
+ box.append(overlay);
+
+ settings.onLoadStart.call(box);
+ }
+
+ function done(box) {
+ //Remove overlay and loading img
+ box.find(overlay).remove();
+
+ settings.onLoadDone.call(box);
+ }
+
+ };
+
+})(jQuery);
+
+/*
+ * EXPLICIT BOX CONTROLS
+ * -----------------------
+ * This is a custom plugin to use with the component BOX. It allows you to activate
+ * a box inserted in the DOM after the app.js was loaded, toggle and remove box.
+ *
+ * @type plugin
+ * @usage $("#box-widget").activateBox();
+ * @usage $("#box-widget").toggleBox();
+ * @usage $("#box-widget").removeBox();
+ */
+(function ($) {
+
+ 'use strict';
+
+ $.fn.activateBox = function () {
+ $.AdminLTE.boxWidget.activate(this);
+ };
+
+ $.fn.toggleBox = function () {
+ var button = $($.AdminLTE.boxWidget.selectors.collapse, this);
+ $.AdminLTE.boxWidget.collapse(button);
+ };
+
+ $.fn.removeBox = function () {
+ var button = $($.AdminLTE.boxWidget.selectors.remove, this);
+ $.AdminLTE.boxWidget.remove(button);
+ };
+
+})(jQuery);
+
+/*
+ * TODO LIST CUSTOM PLUGIN
+ * -----------------------
+ * This plugin depends on iCheck plugin for checkbox and radio inputs
+ *
+ * @type plugin
+ * @usage $("#todo-widget").todolist( options );
+ */
+(function ($) {
+
+ 'use strict';
+
+ $.fn.todolist = function (options) {
+ // Render options
+ var settings = $.extend({
+ //When the user checks the input
+ onCheck: function (ele) {
+ return ele;
+ },
+ //When the user unchecks the input
+ onUncheck: function (ele) {
+ return ele;
+ }
+ }, options);
+
+ return this.each(function () {
+
+ if (typeof $.fn.iCheck != 'undefined') {
+ $('input', this).on('ifChecked', function () {
+ var ele = $(this).parents("li").first();
+ ele.toggleClass("done");
+ settings.onCheck.call(ele);
+ });
+
+ $('input', this).on('ifUnchecked', function () {
+ var ele = $(this).parents("li").first();
+ ele.toggleClass("done");
+ settings.onUncheck.call(ele);
+ });
+ } else {
+ $('input', this).on('change', function () {
+ var ele = $(this).parents("li").first();
+ ele.toggleClass("done");
+ if ($('input', ele).is(":checked")) {
+ settings.onCheck.call(ele);
+ } else {
+ settings.onUncheck.call(ele);
+ }
+ });
+ }
+ });
+ };
+}(jQuery));
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/attrchange.js b/login/app/sprinkles/core/assets/userfrosting/js/attrchange.js
new file mode 100755
index 0000000..00878d3
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/attrchange.js
@@ -0,0 +1,124 @@
+/*
+A simple jQuery function that can add listeners on attribute change.
+http://meetselva.github.io/attrchange/
+
+About License:
+Copyright (C) 2013-2014 Selvakumar Arumugam
+You may use attrchange plugin under the terms of the MIT Licese.
+https://github.com/meetselva/attrchange/blob/master/MIT-License.txt
+ */
+(function($) {
+ function isDOMAttrModifiedSupported() {
+ var p = document.createElement('p');
+ var flag = false;
+
+ if (p.addEventListener) {
+ p.addEventListener('DOMAttrModified', function() {
+ flag = true
+ }, false);
+ } else if (p.attachEvent) {
+ p.attachEvent('onDOMAttrModified', function() {
+ flag = true
+ });
+ } else { return false; }
+ p.setAttribute('id', 'target');
+ return flag;
+ }
+
+ function checkAttributes(chkAttr, e) {
+ if (chkAttr) {
+ var attributes = this.data('attr-old-value');
+
+ if (e.attributeName.indexOf('style') >= 0) {
+ if (!attributes['style'])
+ attributes['style'] = {}; //initialize
+ var keys = e.attributeName.split('.');
+ e.attributeName = keys[0];
+ e.oldValue = attributes['style'][keys[1]]; //old value
+ e.newValue = keys[1] + ':'
+ + this.prop("style")[$.camelCase(keys[1])]; //new value
+ attributes['style'][keys[1]] = e.newValue;
+ } else {
+ e.oldValue = attributes[e.attributeName];
+ e.newValue = this.attr(e.attributeName);
+ attributes[e.attributeName] = e.newValue;
+ }
+
+ this.data('attr-old-value', attributes); //update the old value object
+ }
+ }
+
+ //initialize Mutation Observer
+ var MutationObserver = window.MutationObserver
+ || window.WebKitMutationObserver;
+
+ $.fn.attrchange = function(a, b) {
+ if (typeof a == 'object') {//core
+ var cfg = {
+ trackValues : false,
+ callback : $.noop
+ };
+ //backward compatibility
+ if (typeof a === "function") { cfg.callback = a; } else { $.extend(cfg, a); }
+
+ if (cfg.trackValues) { //get attributes old value
+ this.each(function(i, el) {
+ var attributes = {};
+ for ( var attr, i = 0, attrs = el.attributes, l = attrs.length; i < l; i++) {
+ attr = attrs.item(i);
+ attributes[attr.nodeName] = attr.value;
+ }
+ $(this).data('attr-old-value', attributes);
+ });
+ }
+
+ if (MutationObserver) { //Modern Browsers supporting MutationObserver
+ var mOptions = {
+ subtree : false,
+ attributes : true,
+ attributeOldValue : cfg.trackValues
+ };
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(e) {
+ var _this = e.target;
+ //get new value if trackValues is true
+ if (cfg.trackValues) {
+ e.newValue = $(_this).attr(e.attributeName);
+ }
+ if ($(_this).data('attrchange-status') === 'connected') { //execute if connected
+ cfg.callback.call(_this, e);
+ }
+ });
+ });
+
+ return this.data('attrchange-method', 'Mutation Observer').data('attrchange-status', 'connected')
+ .data('attrchange-obs', observer).each(function() {
+ observer.observe(this, mOptions);
+ });
+ } else if (isDOMAttrModifiedSupported()) { //Opera
+ //Good old Mutation Events
+ return this.data('attrchange-method', 'DOMAttrModified').data('attrchange-status', 'connected').on('DOMAttrModified', function(event) {
+ if (event.originalEvent) { event = event.originalEvent; }//jQuery normalization is not required
+ event.attributeName = event.attrName; //property names to be consistent with MutationObserver
+ event.oldValue = event.prevValue; //property names to be consistent with MutationObserver
+ if ($(this).data('attrchange-status') === 'connected') { //disconnected logically
+ cfg.callback.call(this, event);
+ }
+ });
+ } else if ('onpropertychange' in document.body) { //works only in IE
+ return this.data('attrchange-method', 'propertychange').data('attrchange-status', 'connected').on('propertychange', function(e) {
+ e.attributeName = window.event.propertyName;
+ //to set the attr old value
+ checkAttributes.call($(this), cfg.trackValues, e);
+ if ($(this).data('attrchange-status') === 'connected') { //disconnected logically
+ cfg.callback.call(this, e);
+ }
+ });
+ }
+ return this;
+ } else if (typeof a == 'string' && $.fn.attrchange.hasOwnProperty('extensions') &&
+ $.fn.attrchange['extensions'].hasOwnProperty(a)) { //extensions/options
+ return $.fn.attrchange['extensions'][a].call(this, b);
+ }
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/fortress-jqueryvalidation-methods.js b/login/app/sprinkles/core/assets/userfrosting/js/fortress-jqueryvalidation-methods.js
new file mode 100755
index 0000000..b008cf2
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/fortress-jqueryvalidation-methods.js
@@ -0,0 +1,57 @@
+$.validator.addMethod("equals", function(value, element, params) {
+ params = $.extend(
+ true, // deep extend
+ {
+ value: '',
+ caseSensitive: false
+ }, params);
+
+ if (!params.caseSensitive) {
+ params.value = params.value.toLowerCase();
+ value = value.toLowerCase();
+ }
+ return this.optional(element) || value == params.value;
+}, "Value is not correct.");
+
+$.validator.addMethod("notEquals", function(value, element, params) {
+ params = $.extend(
+ true, // deep extend
+ {
+ value: '',
+ caseSensitive: false
+ }, params);
+
+ if (!params.caseSensitive) {
+ params.value = params.value.toLowerCase();
+ value = value.toLowerCase();
+ }
+ return this.optional(element) || value != params.value;
+}, "Value is not correct.");
+
+$.validator.addMethod("noLeadingWhitespace", function(value, element) {
+ return this.optional(element) || /^\S.*$/i.test(value);
+}, "No leading whitespace allowed");
+
+$.validator.addMethod("noTrailingWhitespace", function(value, element) {
+ return this.optional(element) || /^.*\S$/i.test(value);
+}, "No trailing whitespace allowed");
+
+jQuery.validator.addMethod("memberOf", function(value, element, arr) {
+ return $.inArray(value, arr) != -1;
+}, "Data provided must match one of the provided options.");
+
+jQuery.validator.addMethod("notMemberOf", function(value, element, arr) {
+ return $.inArray(value, arr) == -1;
+}, "Data provided must NOT match one of the provided options.");
+
+jQuery.validator.addMethod("matchFormField", function(value, element, field) {
+ return value === $(element).closest('form').find("input[name=" + field + "]").val();
+}, "The specified fields must match.");
+
+jQuery.validator.addMethod("notMatchFormField", function(value, element, field) {
+ return value !== $(element).closest('form').find("input[name=" + field + "]").val();
+}, "The specified fields must NOT match.");
+
+$.validator.addMethod("username", function(value, element) {
+ return this.optional(element) || /^([a-z0-9\.\-_])+$/.test(value);
+}, "The field may only contain lowercase letters, digits, '.', '-', and '_'.");
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/handlebars-helpers.js b/login/app/sprinkles/core/assets/userfrosting/js/handlebars-helpers.js
new file mode 100755
index 0000000..96f47bb
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/handlebars-helpers.js
@@ -0,0 +1,119 @@
+/**
+ * This file contains extra helper functions for Handlebars.js.
+ *
+ * @see http://handlebarsjs.com/#helpers
+ */
+
+ /**
+ * Improved comparison operator
+ * See https://stackoverflow.com/a/16315366/2970321
+ */
+Handlebars.registerHelper('ifx', function (v1, operator, v2, options) {
+ switch (operator) {
+ case '==':
+ return (v1 == v2) ? options.fn(this) : options.inverse(this);
+ case '===':
+ return (v1 === v2) ? options.fn(this) : options.inverse(this);
+ case '!=':
+ return (v1 != v2) ? options.fn(this) : options.inverse(this);
+ case '!==':
+ return (v1 !== v2) ? options.fn(this) : options.inverse(this);
+ case '<':
+ return (v1 < v2) ? options.fn(this) : options.inverse(this);
+ case '<=':
+ return (v1 <= v2) ? options.fn(this) : options.inverse(this);
+ case '>':
+ return (v1 > v2) ? options.fn(this) : options.inverse(this);
+ case '>=':
+ return (v1 >= v2) ? options.fn(this) : options.inverse(this);
+ case '&&':
+ return (v1 && v2) ? options.fn(this) : options.inverse(this);
+ case '||':
+ return (v1 || v2) ? options.fn(this) : options.inverse(this);
+ default:
+ return (v1 == v2) ? options.fn(this) : options.inverse(this);
+ }
+});
+
+/**
+ * Perform simple calculations.
+ *
+ * usage: {{calc x '+' 2}}
+ */
+Handlebars.registerHelper('calc', function (v1, operator, v2, options) {
+ lvalue = parseFloat(v1);
+ rvalue = parseFloat(v2);
+
+ return {
+ "+": lvalue + rvalue,
+ "-": lvalue - rvalue,
+ "*": lvalue * rvalue,
+ "/": lvalue / rvalue,
+ "%": lvalue % rvalue
+ }[operator];
+});
+
+/**
+ * format an ISO date using Moment.js
+ *
+ * moment syntax example: moment(Date("2011-07-18T15:50:52")).format("MMMM YYYY")
+ * usage: {{dateFormat creation_date format="MMMM YYYY"}}
+ * @requires momentjs http://momentjs.com/
+ */
+Handlebars.registerHelper('dateFormat', function(context, block) {
+ if (window.moment) {
+ var f = block.hash.format || "MMM Do, YYYY";
+ return moment(context).format(f);
+ } else {
+ // moment plugin not available. return data as is.
+ console.log("The moment.js plugin is not loaded. Please make sure you have included moment.js on this page.");
+ return context;
+ }
+});
+
+/**
+ * Format a phone number.
+ */
+Handlebars.registerHelper("phoneUSFormat", function(phoneNumber) {
+ if (typeof phoneNumber === 'undefined') {
+ return '';
+ }
+
+ phoneNumber = phoneNumber.toString();
+ return "(" + phoneNumber.substr(0,3) + ") " + phoneNumber.substr(3,3) + "-" + phoneNumber.substr(6,4);
+});
+
+/**
+ * Format currency (USD).
+ */
+Handlebars.registerHelper("currencyUsdFormat", function(amount) {
+ var parsedAmount = parseFloat(amount);
+ if (parsedAmount < 0) {
+ return "-$" + Math.abs(parsedAmount).toFixed(2);
+ } else {
+ return "$" + parsedAmount.toFixed(2);
+ }
+});
+
+/**
+ * Convert a string to a slug using speakingurl.js.
+ *
+ * @requires speakingurl https://pid.github.io/speakingurl/
+ */
+Handlebars.registerHelper('slug', function(text) {
+ return getSlug(text);
+});
+
+/**
+ * Equality helper for Handlebars
+ * http://stackoverflow.com/questions/8853396/logical-operator-in-a-handlebars-js-if-conditional/21915381#21915381
+ * @deprecated since 4.1 - use ifx instead
+ * usage: {{ifCond apple orange}}
+ */
+Handlebars.registerHelper('ifCond', function(v1, v2, options) {
+ if(v1 == v2) {
+ return options.fn(this);
+ }
+
+ return options.inverse(this);
+});
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/query-string.js b/login/app/sprinkles/core/assets/userfrosting/js/query-string.js
new file mode 100755
index 0000000..5e0d780
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/query-string.js
@@ -0,0 +1,65 @@
+/**
+ * @add jQuery.String
+ */
+$.String = $.extend($.String || {}, {
+ /**
+ * @function deparam
+ *
+ * Takes a string of name value pairs and returns a Object literal that represents those params.
+ *
+ * @param {String} params a string like "foo=bar&person[age]=3"
+ * @return {Object} A JavaScript Object that represents the params:
+ *
+ * {
+ * foo: "bar",
+ * person: {
+ * age: "3"
+ * }
+ * }
+ */
+ deparam: function(params){
+ var digitTest = /^\d+$/,
+ keyBreaker = /([^\[\]]+)|(\[\])/g,
+ plus = /\+/g,
+ paramTest = /([^?#]*)(#.*)?$/;
+
+ if(! params || ! paramTest.test(params) ) {
+ return {};
+ }
+
+
+ var data = {},
+ pairs = params.split('&'),
+ current;
+
+ for(var i=0; i < pairs.length; i++){
+ current = data;
+ var pair = pairs[i].split('=');
+
+ // if we find foo=1+1=2
+ if(pair.length != 2) {
+ pair = [pair[0], pair.slice(1).join("=")]
+ }
+
+ var key = decodeURIComponent(pair[0].replace(plus, " ")),
+ value = decodeURIComponent(pair[1].replace(plus, " ")),
+ parts = key.match(keyBreaker);
+
+ for ( var j = 0; j < parts.length - 1; j++ ) {
+ var part = parts[j];
+ if (!current[part] ) {
+ // if what we are pointing to looks like an array
+ current[part] = digitTest.test(parts[j+1]) || parts[j+1] == "[]" ? [] : {}
+ }
+ current = current[part];
+ }
+ lastPart = parts[parts.length - 1];
+ if(lastPart == "[]"){
+ current.push(value)
+ }else{
+ current[lastPart] = value;
+ }
+ }
+ return data;
+ }
+});
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/tablesorter/widget-sort2Hash.js b/login/app/sprinkles/core/assets/userfrosting/js/tablesorter/widget-sort2Hash.js
new file mode 100755
index 0000000..1be9c85
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/tablesorter/widget-sort2Hash.js
@@ -0,0 +1,271 @@
+/*! Widget: sort2Hash (BETA) - updated 8/12/2017 (v2.28.15.uf) */
+/* Requires tablesorter v2.8+ and jQuery 1.7+
+ * by Rob Garrison
+ *
+ * Temporary patched version of widget to handle browser history issues (#712).
+ */
+;( function( $ ) {
+ 'use strict';
+ var ts = $.tablesorter || {},
+ s2h = ts.sort2Hash = {
+ init : function( c, wo ) {
+ var filter, temp, page, size,
+ table = c.table,
+ pager = c.pager,
+ hasSaveSort = ts.hasWidget( table, 'saveSort' ),
+ sort = s2h.decodeHash( c, wo, 'sort' );
+ if ( ( sort && !hasSaveSort ) || ( sort && hasSaveSort && wo.sort2Hash_overrideSaveSort ) ) {
+ s2h.convertString2Sort( c, wo, sort );
+ }
+ if ( ts.hasWidget( c.table, 'pager' ) ) {
+ temp = parseInt( s2h.decodeHash( c, wo, 'page' ), 10 );
+ page = pager.page = ( temp < 0 ? 0 : ( temp > pager.totalPages ? pager.totalPages - 1 : temp ) );
+ size = pager.size = parseInt( s2h.decodeHash( c, wo, 'size' ), 10 );
+ }
+ if ( ts.hasWidget( table, 'filter' ) ) {
+ filter = s2h.decodeHash( c, wo, 'filter' );
+ if ( filter ) {
+ filter = filter.split( wo.sort2Hash_separator );
+ c.$table.one( 'tablesorter-ready', function() {
+ setTimeout(function(){
+ c.$table.one( 'filterEnd', function() {
+ $(this).triggerHandler( 'pageAndSize', [ page, size ] );
+ });
+ // use the newest filter comparison code
+ if ( ts.filter.equalFilters ) {
+ temp = ts.filter.equalFilters( c, c.lastSearch, filter );
+ } else {
+ // quick n' dirty comparison... it will miss filter changes of
+ // the same value in a different column, see #1363
+ temp = ( c.lastSearch || [] ).join( '' ) !== ( filter || [] ).join( '' );
+ }
+ // don't set filters if they haven't changed
+ if ( !temp ) {
+ $.tablesorter.setFilters( table, filter, true );
+ }
+ }, 100 );
+ });
+ }
+ }
+ if ( !filter ) {
+ c.$table.one( 'tablesorter-ready', function() {
+ c.$table.triggerHandler( 'pageAndSize', [ page, size ] );
+ });
+ }
+
+ c.$table.on( 'sortEnd.sort2hash filterEnd.sort2hash pagerComplete.sort2Hash', function() {
+ if ( this.hasInitialized ) {
+ s2h.setHash( this.config, this.config.widgetOptions );
+ }
+ });
+ },
+
+ getTableId : function( c, wo ) {
+ // option > table id > table index on page
+ return wo.sort2Hash_tableId ||
+ c.table.id ||
+ 'table' + $( 'table' ).index( c.$table );
+ },
+ regexEscape : function( v ) {
+ return v.replace( /([\.\^\$\*\+\-\?\(\)\[\]\{\}\\\|])/g, '\\$1');
+ },
+ // convert 'first%20name,asc,last%20name,desc' into [[0,0], [1,1]]
+ convertString2Sort : function( c, wo, sortHash ) {
+ var regex, column, direction, temp, index, $cell,
+ arry = sortHash.split( wo.sort2Hash_separator ),
+ indx = 0,
+ len = arry.length,
+ sort = [];
+ while ( indx < len ) {
+ // column index or text
+ column = arry[ indx++ ];
+ temp = parseInt( column, 10 );
+ // ignore wo.sort2Hash_useHeaderText setting &
+ // just see if column contains a number
+ if ( isNaN( temp ) || temp > c.columns ) {
+ regex = new RegExp( '(' + s2h.regexEscape( column ) + ')', 'i' );
+ for ( index = 0; index < c.columns; index++ ) {
+ $cell = c.$headerIndexed[ index ];
+ if ( regex.test( $cell.attr( wo.sort2Hash_headerTextAttr ) ) ) {
+ column = index;
+ index = c.columns;
+ }
+ }
+ }
+ direction = arry[ indx++ ];
+ // ignore unpaired values
+ if ( typeof column !== 'undefined' && typeof direction !== 'undefined' ) {
+ // convert text to 0, 1
+ if ( isNaN( direction ) ) {
+ // default to ascending sort
+ direction = direction.indexOf( wo.sort2Hash_directionText[ 1 ] ) > -1 ? 1 : 0;
+ }
+ sort.push( [ column, direction ] );
+ }
+ }
+ if ( sort.length ) {
+ c.sortList = sort;
+ }
+ },
+
+ // convert [[0,0],[1,1]] to 'first%20name,asc,last%20name,desc'
+ convertSort2String : function( c, wo ) {
+ var index, txt, column, direction,
+ sort = [],
+ arry = c.sortList || [],
+ len = arry.length;
+ for ( index = 0; index < len; index++ ) {
+ column = arry[ index ][ 0 ];
+ txt = $.trim( c.$headerIndexed[ column ].attr( wo.sort2Hash_headerTextAttr ) );
+ sort.push( txt !== '' ? encodeURIComponent( txt ) : column );
+ direction = wo.sort2Hash_directionText[ arry[ index ][ 1 ] ];
+ sort.push( direction );
+ }
+ // join with separator
+ return sort.join( wo.sort2Hash_separator );
+ },
+
+ convertFilter2String : function( c, wo ) {
+ var index, txt, column, direction,
+ sort = [],
+ arry = c.sortList || [],
+ len = arry.length;
+ for ( index = 0; index < len; index++ ) {
+ column = arry[ index ][ 0 ];
+ txt = $.trim( c.$headerIndexed[ column ].attr( wo.sort2Hash_headerTextAttr ) );
+ column = typeof txt !== 'undefined' ? encodeURIComponent( txt ) : column;
+ sort.push( column );
+ direction = wo.sort2Hash_directionText[ arry[ index ][ 1 ] ];
+ sort.push( direction );
+ }
+ // join with separator
+ return sort.join( wo.sort2Hash_separator );
+ },
+
+ // Get URL Parameters (getParam)
+ // modified from http://www.netlobo.com/url_query_string_javascript.html
+ getParam : function ( name, hash, returnRegex ) {
+ if ( !hash ) { hash = window.location.hash; }
+ var regex = new RegExp( '[\\?&]' + s2h.regexEscape( name ) + '=([^]*)' ),
+ match = regex.exec( hash );
+ if ( returnRegex ) { return regex; }
+ return match === null ? '' : decodeURIComponent( match[ 1 ] );
+ },
+
+ // remove parameter from hash
+ removeParam : function( name, hash ) {
+ if ( !hash ) { hash = window.location.hash; }
+ var index,
+ regex = s2h.getParam( name, hash, true ),
+ result = [],
+ parts = hash.split( '&' ),
+ len = parts.length;
+ for ( index = 0; index < len; index++ ) {
+ // regex expects a leading '&'...
+ if ( !regex.test( '&' + parts[ index ] ) ) {
+ result.push( parts[ index ] );
+ }
+ }
+ return result.length ? result.join( '&' ) : '';
+ },
+
+ encodeHash : function( c, wo, component, value, rawValue ) {
+ var result = false,
+ tableId = s2h.getTableId( c, wo );
+ if ( typeof wo.sort2Hash_encodeHash === 'function' ) {
+ result = wo.sort2Hash_encodeHash( c, tableId, component, value, rawValue || value );
+ }
+ if ( result === false ) {
+ result = '&' + component + '[' + tableId + ']=' + value;
+ }
+ return result;
+ },
+
+ decodeHash : function( c, wo, component ) {
+ var result = false,
+ tableId = s2h.getTableId( c, wo );
+ if ( typeof wo.sort2Hash_decodeHash === 'function' ) {
+ // return a string
+ result = wo.sort2Hash_decodeHash( c, tableId, component );
+ }
+ if ( result === false ) {
+ result = s2h.getParam( component + '[' + tableId + ']' );
+ }
+ return result || '';
+ },
+
+ cleanHash : function( c, wo, component, hash ) {
+ var result = false,
+ tableId = s2h.getTableId( c, wo );
+ if ( typeof wo.sort2Hash_cleanHash === 'function' ) {
+ // can return an array or string
+ result = wo.sort2Hash_cleanHash( c, tableId, component, hash );
+ }
+ if ( result === false ) {
+ // parameter example: 'sort[table0]=0,0'
+ result = s2h.removeParam( component + '[' + tableId + ']', hash );
+ }
+ return result || '';
+ },
+
+ setHash : function( c, wo ) {
+ var str = '',
+ hash = window.location.hash,
+ hasPager = ts.hasWidget( c.table, 'pager' ),
+ hasFilter = ts.hasWidget( c.table, 'filter' ),
+ sortList = s2h.convertSort2String( c, wo ),
+ filters = ( hasFilter && c.lastSearch.join('') !== '' ? c.lastSearch : [] ),
+ filtersStr = encodeURIComponent( filters.join( c.widgetOptions.sort2Hash_separator ) ),
+ components = {
+ 'sort' : sortList ? s2h.encodeHash( c, wo, 'sort', sortList, c.sortList ) : '',
+ 'page' : hasPager ? s2h.encodeHash( c, wo, 'page', c.pager.page + 1 ) : '',
+ 'size' : hasPager ? s2h.encodeHash( c, wo, 'size', c.pager.size ) : '',
+ 'filter' : filtersStr ? s2h.encodeHash( c, wo, 'filter', filtersStr, filters ) : ''
+ };
+ // remove old hash
+ $.each( components, function( component, value ) {
+ hash = s2h.cleanHash( c, wo, component, hash );
+ str += value;
+ });
+
+ // Combine new hash with any existing hashes
+ var hashChar = c.widgetOptions.sort2Hash_hash;
+ var newHash = ( ( window.location.hash || '' ).replace( hashChar, '' ).length ? hash : wo.sort2Hash_hash ) + str;
+ var baseUrl = window.location.href.split(hashChar)[0];
+ // Ensure that there is a leading hash character
+ var firstChar = newHash[0];
+ if (firstChar != hashChar) {
+ newHash = hashChar + newHash;
+ }
+
+ // Update URL in browser
+ window.location.replace(baseUrl + newHash);
+ }
+ };
+
+ ts.addWidget({
+ id: 'sort2Hash',
+ priority: 60, // after saveSort & pager
+ options: {
+ sort2Hash_hash : '#', // hash prefix
+ sort2Hash_separator : '-', // don't '#' or '=' here
+ sort2Hash_headerTextAttr : 'data-header', // data attribute containing alternate header text
+ sort2Hash_directionText : [ 0, 1 ], // [ 'asc', 'desc' ],
+ sort2Hash_overrideSaveSort : false, // if true, override saveSort widget if saved sort available
+
+ // this option > table ID > table index on page
+ sort2Hash_tableId : null,
+ // custom hash processing functions
+ sort2Hash_encodeHash : null,
+ sort2Hash_decodeHash : null,
+ sort2Hash_cleanHash : null
+ },
+ init: function(table, thisWidget, c, wo) {
+ s2h.init( c, wo );
+ },
+ remove: function(table, c) {
+ c.$table.off( '.sort2hash' );
+ }
+ });
+
+})(jQuery);
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/uf-alerts.js b/login/app/sprinkles/core/assets/userfrosting/js/uf-alerts.js
new file mode 100755
index 0000000..06a889c
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/uf-alerts.js
@@ -0,0 +1,285 @@
+/**
+ * ufAlerts jQuery plugin. Fetches and renders alerts from the UF alert stream.
+ *
+ * Based on template from https://github.com/jquery-boilerplate/jquery-boilerplate
+ *
+ * === USAGE ===
+
+ * ufAlerts can be initialized on any container element as follows:
+ *
+ * $('#myDiv').ufAlerts(options);
+ *
+ * `options` is an object containing any of the following parameters:
+ * @param {string} url The absolute URL from which to fetch flash alerts.
+ * @param {bool} scrollToTop Whether to automatically scroll back to the top of the page after rendering alerts.
+ * @param {string} alertMessageClass The CSS class(es) to be applied to each alert message.
+ * @param {string} alertTemplateId The CSS id(es) for the Handlebar alert template.
+ * @param {bool} agglomerate Set to true to render all alerts together, applying styling for the highest-priority alert being rendered.
+ *
+ * == EVENTS ==
+ *
+ * uf-form triggers the following events:
+ *
+ * `fetch.ufAlerts`: triggered when the alerts have been successfully fetched from the server.
+ * `render.ufAlerts`: triggered when all alerts have been rendered and the call to render() has completed.
+ *
+ * == METHODS ==
+ *
+ * `fetch()`: Asynchronously gets alerts from the server.
+ * `push(options)`: Adds a alert of a specified type (danger, warning, info, success) to the internal collection of alerts.
+ * `clear()`: Removes all messages from the internal collection.
+ * `render()`: Renders the collection of alerts to the container, awaiting results of `fetch()` if required.
+ *
+ * UserFrosting https://www.userfrosting.com
+ * @author Alexander Weissman
+ */
+;(function($, window, document, undefined) {
+ 'use strict';
+
+ // Define plugin name and defaults.
+ var pluginName = 'ufAlerts',
+ defaults = {
+ url : site.uri.public + '/alerts',
+ scrollToTop : true,
+ scrollWhenVisible : false,
+ agglomerate : false,
+ alertMessageClass : 'uf-alert-message',
+ alertTemplateId : 'uf-alert-template',
+ DEBUG : false
+ };
+
+ // Constructor
+ function Plugin (element, options) {
+ this.element = element[0];
+ this.$element = $(this.element);
+ this.settings = $.extend(true, {}, defaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+
+ // Detect changes to element attributes
+ this.$element.attrchange({ callback: function (event) { this.element = event.target; }.bind(this) });
+
+ // Plugin variables
+ this.alerts = [];
+ this._newAlertsPromise = $.Deferred().resolve();
+ this._alertTemplateHtml = $('#' + this.settings.alertTemplateId).html();
+ this._alertTypePriorities = {
+ danger : 3,
+ warning: 2,
+ success: 1,
+ info : 0
+ };
+ this._alertTypeIcon = {
+ danger : 'fa-ban',
+ warning: 'fa-warning',
+ success: 'fa-check',
+ info : 'fa-info'
+ };
+
+ return this;
+ }
+
+ // Functions
+ $.extend(Plugin.prototype, {
+ /**
+ * Clear all alerts from the current uf-alerts collection.
+ */
+ clear: function() {
+ // See http://stackoverflow.com/a/1232046/2970321
+ this.alerts.length = 0;
+
+ if (this.settings.agglomerate) {
+ this.element.toggleClass('alert', false)
+ .toggleClass('alert-info', false)
+ .toggleClass('alert-success', false)
+ .toggleClass('alert-warning', false)
+ .toggleClass('alert-danger', false);
+ }
+
+ // Clear any alert HTML
+ this.$element.empty();
+
+ return this.$element;
+ },
+ /**
+ * Fetches alerts from the alert stream
+ */
+ fetch: function() {
+ // Set a promise, so that any chained calls after fetch can wait until the messages have been retrieved
+ this._newAlertsPromise = $.ajax({
+ url: this.settings.url,
+ cache: false
+ }).then(
+ // Success
+ this._fetchSuccess.bind(this),
+ // Failure
+ this._fetchFailure.bind(this)
+ );
+
+ return this.$element;
+ },
+ /**
+ * Success callback for fetch
+ */
+ _fetchSuccess: function(alerts) {
+ if (alerts != null) this.alerts = $.merge(this.alerts, alerts);
+ this.$element.trigger('fetch.' + this._name);
+ },
+ /**
+ * Failure callback for fetch
+ */
+ _fetchFailure: function(response) {
+ this.$element.trigger('error.' + this._name);
+ if ((typeof site !== 'undefined') && site.debug.ajax && response.responseText) {
+ document.write(response.responseText);
+ document.close();
+ } else {
+ if (this.settings.DEBUG) {
+ console.warn('Error (' + response.status + '): ' + response.responseText );
+ }
+ }
+ },
+ /**
+ * Push a given message to the current uf-alerts collection.
+ */
+ push: function(options) {
+ this.alerts.push({
+ type : options[0],
+ message: options[1]
+ });
+
+ return this.$element;
+ },
+ /**
+ * Renders the alerts.
+ */
+ render: function() {
+ // Wait for promise completion, only if promise is unresolved.
+ if (this._newAlertsPromise.state() == 'resolved' || this._newAlertsPromise.state() == 'rejected') {
+ this._render();
+ }
+ else {
+ $.when(this._newAlertsPromise).then(this._render.bind(this));
+ }
+
+ return this.$element;
+ },
+ /*
+ * Internal private method that physically handles rendering operation.
+ */
+ _render: function() {
+ // Holds generated HTML
+ var alertHtml = '';
+ // Only compile alerts if there are alerts to display
+ if (this.alerts.length > 0) {
+ // Prepare template
+ var alertTemplate = Handlebars.compile(this._alertTemplateHtml, {noEscape: true});
+ var i;
+ // If agglomeration is enabled, set the container to the highest priority alert type
+ if (this.settings.agglomerate) {
+ // Holds generated agglomerated alerts
+ var alertMessage = '
';
+
+ // Determine overall alert priority
+ var alertContainerType = 'info';
+ for (i = 0; i < this.alerts.length; i++) {
+ if (this._alertTypePriorities[this.alerts[i].type] > this._alertTypePriorities[alertContainerType]) {
+ alertContainerType = this.alerts[i].type;
+ }
+ }
+
+ // Compile each alert
+ var aggTemplate = Handlebars.compile('
{{ message }}
');
+ for (i = 0; i < this.alerts.length; i++) {
+ alertMessage += aggTemplate(this.alerts[i]);
+ }
+
+ alertMessage += '
';
+
+ // Generate complete alert HTML
+ alertHtml = alertTemplate({
+ type : alertContainerType,
+ message: alertMessage,
+ icon : this._alertTypeIcon[alertContainerType]
+ });
+ }
+ else {
+ // Compile each alert.
+ for (i = 0; i < this.alerts.length; i++) {
+ var alert = this.alerts[i];
+
+ // Inject icon
+ alert.icon = this._alertTypeIcon[alert.type];
+
+ // Compile alert
+ alertHtml += alertTemplate(alert);
+ }
+ }
+ }
+ // Show alerts
+ this.$element.html(alertHtml);
+
+ // Scroll to top of alert location is new alerts output, and auto scrolling is enabled
+ if (this.settings.scrollToTop && alertHtml !== '') {
+ // Don't scroll if already visible, unless scrollWhenVisible is true
+ if (!this._alertsVisible() || this.settings.scrollWhenVisible) {
+ $('html, body').animate({ scrollTop: this.$element.offset().top }, 'fast');
+ }
+ }
+
+ // Trigger render events
+ this.$element.trigger('render.' + this._name);
+ },
+ /**
+ * Returns true if alerts container is completely within the viewport.
+ */
+ _alertsVisible: function() {
+ var rect = this.element.getBoundingClientRect();
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth)
+ );
+ },
+ /**
+ * Completely destroy the ufAlerts plugin on the element.
+ */
+ destroy: function() {
+ // Unbind any bound events
+ this.$element.off('.' + this._name);
+
+ // Remove plugin from element
+ this.$element.removeData(this._name);
+
+ return this.$element;
+ }
+ });
+
+ // Handles instantiation and access to non-private methods.
+ $.fn[pluginName] = function(methodOrOptions) {
+ // Grab plugin instance
+ var instance = $(this).data(pluginName);
+ // If undefined or object, initalise plugin.
+ if (methodOrOptions === undefined || typeof methodOrOptions === 'object') {
+ // Only initalise if not previously done.
+ if (!instance) {
+ $(this).data(pluginName, new Plugin(this, methodOrOptions));
+ }
+ return this;
+ }
+ // Otherwise ensure first parameter is a valid string, and is the name of an actual function.
+ else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') {
+ // Ensure not a private function
+ if (methodOrOptions.indexOf('_') !== 0) {
+ return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1));
+ }
+ else {
+ console.warn('Method ' + methodOrOptions + ' is private!');
+ }
+ }
+ else {
+ console.warn('Method ' + methodOrOptions + ' does not exist.');
+ }
+ };
+})(jQuery, window, document);
\ No newline at end of file
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/uf-captcha.js b/login/app/sprinkles/core/assets/userfrosting/js/uf-captcha.js
new file mode 100755
index 0000000..ed3ecea
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/uf-captcha.js
@@ -0,0 +1,13 @@
+/**
+ * This plugin reloads the captcha in the specified element.
+ */
+(function( $ ) {
+ $.fn.captcha = function() {
+ // Set the new captcha image
+ $(this).attr('src', site.uri.public + "/account/captcha?" + new Date().getTime());
+
+ // Clear whatever the user entered for the captcha value last time
+ var target = $(this).data('target');
+ $(target).val("");
+ };
+}( jQuery ));
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/uf-collection.js b/login/app/sprinkles/core/assets/userfrosting/js/uf-collection.js
new file mode 100755
index 0000000..a2afc6e
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/uf-collection.js
@@ -0,0 +1,345 @@
+/**
+ * uf-collection plugin. Widget for attaching/detaching related items to a single parent item (e.g. roles for a user, etc).
+ *
+ * === USAGE ===
+ *
+ * uf-collection can be initialized on a div element as follows:
+ *
+ * $("#myCollection").ufCollection(options);
+ *
+ * `options` is an object containing any of the following parameters:
+ * @param {bool} useDropdown Set to true if rows should be added using a select2 dropdown, false for free text inputs (see https://ux.stackexchange.com/a/15637/53990).
+ * @param {Object} dropdown The options to pass to the select2 plugin for the add item dropdown.
+ * @param {string} dropdown.ajax.url The url from which to fetch options (as JSON data) in the dropdown selector menu.
+ * @param {bool} selectOnClose Set to true if you want the currently highlighted dropdown item to be automatically added when the dropdown is closed for any reason.
+ * @param {string} dropdown.theme The select2 theme to use for the dropdown menu. Defaults to "default".
+ * @param {string} dropdown.placeholder Placeholder text to use in the dropdown menu before a selection is made. Defaults to "Item".
+ * @param {string} dropdown.width Width of the dropdown selector, when used. Defaults to "100%".
+ * @param {callback} transformDropdownSelection Custom transformation on objects from the dropdown before passing them to render in the collection table.
+ * @param {Object} dropdownControl a jQuery selector specifying the dropdown select2 control. Defaults to looking for a .js-select-new element inside the parent object.
+ * @param {string} dropdownTemplate A Handlebars template to use for rendering the dropdown items.
+ * @param {Object} rowContainer a jQuery selector specifying the place where rows should be added. Defaults to looking for the first tbody element inside the parent object.
+ * @param {string} rowTemplate A Handlebars template to use for rendering each row in the table.
+ *
+ * == EVENTS ==
+ *
+ * ufCollection triggers the following events:
+ *
+ * `rowAdd.ufCollection`: triggered when a new row is added to the collection.
+ * `rowDelete.ufCollection`: triggered when a row is removed from the collection.
+ * `rowTouch.ufCollection`: triggered when any inputs in a row are brought into focus.
+ *
+ * UserFrosting https://www.userfrosting.com
+ * @author Alexander Weissman
+ */
+;(function($, window, document, undefined) {
+ "use strict";
+
+ // Define plugin name and defaults.
+ var pluginName = "ufCollection",
+ defaults = {
+ useDropdown: true,
+ dropdown: {
+ ajax : {
+ url : "",
+ dataType : "json",
+ delay : 250,
+ data : function (params) {
+ return {
+ filters: {
+ info : params.term
+ }
+ };
+ },
+ processResults : function (data, params) {
+ // Process the data into dropdown options
+ var suggestions = [];
+ if (data && data.rows) {
+ suggestions = data.rows;
+ }
+ return {
+ results: suggestions
+ };
+ },
+ cache : true
+ },
+ placeholder : "Item",
+ selectOnClose : false, // Make a selection when they click out of the box/press the next button
+ theme : "default",
+ width : "100%",
+ },
+ transformDropdownSelection: function (item) {
+ return item;
+ },
+ dropdownControl : null,
+ dropdownTemplate: "",
+ rowContainer : null,
+ rowTemplate : "",
+ DEBUG : false
+ };
+
+ // Constructor
+ function Plugin (element, options) {
+ this.element = element[0];
+ this.$element = $(this.element);
+ var lateDefaults = {
+ dropdownControl: this.$element.find('.js-select-new'),
+ rowContainer: this.$element.find('tbody').first()
+ };
+ this.settings = $.extend(true, {}, defaults, lateDefaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+
+ // Detect changes to element attributes
+ this.$element.attrchange({ callback: function (event) { this.element = event.target; }.bind(this) });
+
+ // Internal counter for adding rows to the collection. Gets updated every time `_createRow` is called.
+ this._rownum = 0;
+
+ // Keep track of last added row
+ this._lastRow = null;
+
+ // Handlebars template method
+ this._dropdownTemplateCompiled = Handlebars.compile(this.settings.dropdownTemplate);
+
+ this._rowTemplateCompiled = Handlebars.compile(this.settings.rowTemplate);
+
+ // Add container class
+ this.$element.toggleClass("uf-collection", true);
+
+ // Add bindings for any rows already present in the DOM
+ $.each(this.settings.rowContainer.find('.uf-collection-row'), $.proxy(function(idx, row) {
+ this._onNewRow($(row));
+ this._lastRow = row;
+ }, this));
+
+ // If we're using dropdown options, create the select2 and add bindings to add a new row when an option is selected
+ if (this.settings.useDropdown) {
+ this._initDropdownField(this.settings.dropdownControl);
+
+ this.settings.dropdownControl.on("select2:select", $.proxy(function(e) {
+ var item = $(e.target).select2("data")[0];
+ // Apply any transformations before rendering as a row
+ var transformed = this.settings.transformDropdownSelection(item);
+ this._createRow(transformed);
+ }, this));
+ }
+ else {
+ // Otherwise, add a new virgin row
+ this._createVirginRow();
+ }
+
+ return this;
+ }
+
+ // Functions
+ $.extend(Plugin.prototype, {
+ /**
+ * Add a new row to the collection, optionally passing in prepopulated template data.
+ */
+ addRow: function(options) {
+ // Grab params, if any
+ var params = {};
+ if (typeof options !== 'undefined') {
+ params = options[0];
+ }
+
+ this._createRow(params);
+
+ return this.$element;
+ },
+ /**
+ * Add a new 'virgin' row to the collection, optionally passing in prepopulated template data.
+ * Virgin rows are rows that have not yet been brought into focus by the user.
+ * When a virgin row is brought into focus, it loses its virgin status and a new virgin row is created.
+ */
+ addVirginRow: function(options) {
+ // Grab params, if any
+ var params = {};
+ if (typeof options !== 'undefined') {
+ params = options[0];
+ }
+
+ this._createVirginRow(params);
+
+ return this.$element;
+ },
+ /**
+ * Delete a target row.
+ */
+ deleteRow: function(row) {
+ this._deleteRow(row);
+
+ return this.$element;
+ },
+ /**
+ * Get the dropdown control for the collection, if one exists.
+ */
+ getDropdown: function() {
+ return this.settings.dropdownControl;
+ },
+ /**
+ * Get the last row added in the collection.
+ */
+ getLastRow: function () {
+ return this._lastRow;
+ },
+ /**
+ * Touch a target row.
+ */
+ touchRow: function(row) {
+ this._touchRow(row);
+
+ return this.$element;
+ },
+ /**
+ * Create a new row and attach the handler for deletion to the js-delete-row button
+ */
+ _createRow: function(params) {
+ params = $.extend(true,
+ {
+ id: "",
+ rownum: this._rownum
+ }, params);
+
+ // Generate the row and append to table
+ var newRowTemplate = this._rowTemplateCompiled(params),
+ newRow;
+
+ // Add the new row before any virgin rows in the table.
+ var virginRows = this.settings.rowContainer.find('.uf-collection-row-virgin').length;
+ if (virginRows) {
+ newRow = $(newRowTemplate).insertBefore(this.settings.rowContainer.find('.uf-collection-row-virgin:first'));
+ } else {
+ newRow = $(newRowTemplate).appendTo(this.settings.rowContainer);
+ }
+
+ this._lastRow = newRow;
+
+ // Add bindings and fire event
+ this._onNewRow(newRow);
+
+ return newRow;
+ },
+ /**
+ * Create a new, blank row with the 'virgin' status.
+ */
+ _createVirginRow: function(params) {
+ // Generate the row and append to table
+ var newRow = this._createRow(params);
+
+ // Set the row's 'virgin' status
+ newRow.addClass('uf-collection-row-virgin');
+ newRow.find('.js-delete-row').hide();
+
+ return newRow;
+ },
+ /**
+ * Delete a row from the collection.
+ */
+ _deleteRow: function(row) {
+ row.remove();
+ this.$element.trigger('rowDelete.ufCollection');
+ },
+ /**
+ * Add delete and touch bindings for a row, increment the internal row counter, and fire the rowAdd event
+ */
+ _onNewRow: function(row) {
+ // Trigger to delete row
+ row.find('.js-delete-row').on('click', $.proxy(function(e) {
+ this._deleteRow($(e.target).closest('.uf-collection-row'));
+ }, this));
+
+ // Once the new row comes into focus for the first time, it has been "touched"
+ row.find(':input').on('focus', $.proxy(function() {
+ this._touchRow(row);
+ }, this));
+
+ this._rownum += 1;
+
+ // Fire event when row has been constructed
+ this.$element.trigger('rowAdd.ufCollection', row);
+ },
+ /**
+ * Remove a row's virgin status, show the delete button, and add a new virgin row if needed
+ */
+ _touchRow: function(row) {
+ row.removeClass('uf-collection-row-virgin');
+ row.find('.js-delete-row').show();
+
+ this.$element.trigger('rowTouch.ufCollection', row);
+
+ // If we're not using dropdowns, assert that the table doesn't already have a virgin row. If not, create a new virgin row.
+ if (!this.settings.useDropdown) {
+ var virginRows = this.settings.rowContainer.find('.uf-collection-row-virgin').length;
+ if (!virginRows) {
+ this._createVirginRow();
+ }
+ }
+ },
+ /**
+ * Initialize the select2 dropdown for this collection on a specified control element.
+ */
+ _initDropdownField: function(field) {
+ var options = this.settings.dropdown;
+
+ if (!("templateResult" in options)) {
+ options.templateResult = $.proxy(function(item) {
+ // Display loading text if the item is marked as "loading"
+ if (item.loading) return item.text;
+
+ // Must wrap this in a jQuery selector to render as HTML
+ return $(this._dropdownTemplateCompiled(item));
+ }, this);
+ }
+ // Legacy options (<= v4.0.9)
+ if ("dataUrl" in this.settings) {
+ options.ajax.url = this.settings.dataUrl;
+ }
+ if ("ajaxDelay" in this.settings) {
+ options.ajax.delay = this.settings.ajaxDelay;
+ }
+ if ("dropdownTheme" in this.settings) {
+ options.theme = this.settings.dropdownTheme;
+ }
+ if ("placeholder" in this.settings) {
+ options.placeholder = this.settings.placeholder;
+ }
+ if ("selectOnClose" in this.settings) {
+ options.selectOnClose = this.settings.selectOnClose;
+ }
+ if ("width" in this.settings) {
+ options.width = this.settings.width;
+ }
+
+ return field.select2(options);
+ }
+ });
+
+ // Handles instantiation and access to non-private methods.
+ $.fn[pluginName] = function(methodOrOptions) {
+ // Grab plugin instance
+ var instance = $(this).data(pluginName);
+ // If undefined or object, initalise plugin.
+ if (methodOrOptions === undefined || typeof methodOrOptions === 'object') {
+ // Only initalise if not previously done.
+ if (!instance) {
+ $(this).data(pluginName, new Plugin(this, methodOrOptions));
+ }
+ return this;
+ }
+ // Otherwise ensure first parameter is a valid string, and is the name of an actual function.
+ else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') {
+ // Ensure not a private function
+ if (methodOrOptions.indexOf('_') !== 0) {
+ return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1));
+ }
+ else {
+ console.warn( 'Method ' + methodOrOptions + ' is private!' );
+ }
+ }
+ else {
+ console.warn( 'Method ' + methodOrOptions + ' does not exist.' );
+ }
+ };
+})(jQuery, window, document);
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/uf-copy.js b/login/app/sprinkles/core/assets/userfrosting/js/uf-copy.js
new file mode 100755
index 0000000..a47f6b8
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/uf-copy.js
@@ -0,0 +1,50 @@
+/**
+ * Copies text or control fields to clipboard. Wrap a .js-copy-target and .js-copy-trigger inside a common .js-copy-container.
+ */
+
+if (typeof $.uf === 'undefined') {
+ $.uf = {};
+}
+
+$.uf.copy = function (button) {
+ var _this = this;
+
+ var clipboard = new Clipboard(button, {
+ text: function(trigger) {
+ var el = $(trigger).closest('.js-copy-container').find('.js-copy-target');
+ if (el.is(':input')) {
+ return el.val();
+ } else {
+ return el.html();
+ }
+ }
+ });
+
+ clipboard.on('success', function(e) {
+ setTooltip(e.trigger, 'Copied!');
+ hideTooltip(e.trigger);
+ });
+
+ clipboard.on('error', function(e) {
+ setTooltip(e.trigger, 'Failed!');
+ hideTooltip(e.trigger);
+ });
+
+ function setTooltip(btn, message) {
+ $(btn)
+ .attr('data-original-title', message)
+ .tooltip('show');
+ }
+
+ function hideTooltip(btn) {
+ setTimeout(function() {
+ $(btn).tooltip('hide')
+ .attr('data-original-title', "");
+ }, 1000);
+ }
+
+ // Tooltip
+ $(button).tooltip({
+ trigger: 'click'
+ });
+};
diff --git a/login/app/sprinkles/core/assets/userfrosting/js/uf-form.js b/login/app/sprinkles/core/assets/userfrosting/js/uf-form.js
new file mode 100755
index 0000000..15952ab
--- /dev/null
+++ b/login/app/sprinkles/core/assets/userfrosting/js/uf-form.js
@@ -0,0 +1,297 @@
+/**
+ * uf-form plugin. Handles validation and submission for basic UserFrosting forms.
+ *
+ * This plugin uses the jQueryvalidation plugin (https://jqueryvalidation.org/) to perform instant, client-side form validation.
+ * UserFrosting forms must be wrapped in a