Source: util-jquery.js

/*!
 * util-jquery
 * https://github.com/Voliware/Util
 * Licensed under the MIT license.
 */

if(!isDefined($))
	throw new ReferenceError("util-jquery requires jquery 2.2.2 or greater");

// helpers
if(typeof isJquery === 'undefined'){
	window.isJquery = function(x){
		return x instanceof $;
	}
}



(function($) {

	/**
	 * Checks if an element has an attribute
	 * @param {string} attr - attribute name
	 * @returns {boolean} - true if it does, false otherwise
	 */
	$.fn.hasAttr = function(attr){
		return $(this).is('['+attr+']');
	};

	/**
	 * Populate a DOM object in the appropriate way.
	 * Extend with $Util.populate object
	 * @param {string|number|jQuery} data
	 * @param {boolean} [trigger=true] - whether to call change and input events
	 */
	$.fn.populate = function(data, trigger = true){
		var $this = $(this);

		// don't popualte if data-populate=false
		if($this.data('populate') === false)
			return this;

		var tag = $this.prop("tagName").toLowerCase();
		var type = $this.attr('type');

		// populate using extensions or defaults
		var extension = getExtension(tag);
		if(extension)
			extension.call(this, data);
		else
			defaultPopulate(tag, type, data);

		// prevent further populates if update is set to false
		if($this.data('update') === false)
			this.attr('data-populate', false);

		// trigger input and change events
		if(trigger)
			$this.trigger('change').trigger('input');

		return this;

		/**
		 * Default populate switch
		 * @param {string} tag - element tag
		 * @param {string} type - element type
		 * @param {*} data - data to populate with
		 */
		function defaultPopulate(tag, type, data){
			switch(tag){
				case 'input':
					_populateInput(type, data);
					break;
				case 'select':
				case 'textarea':
					$this.val(data);
					break;
				case 'img':
					$this.attr('src', data);
					break;
				case "button":
					$this.prop('disabled', data === 0);
					break;
				default:
					$this.html(data);
					break;
			}
		}

		/**
		 * Get the extension for this tag,
		 * or for a data-tag attribute
		 * @param {string} tag - element tag
		 * @returns {function|null}
		 */
		function getExtension(tag){
			if($Util.populate[tag])
				return $Util.populate[tag];
			if($this.hasAttr('data-tag'))
				return $Util.populate[$this.attr('data-tag')];
			return null;
		}

		/**
		 * Populate an input according to type
		 */
		function _populateInput(type, data){
			switch(type){
				case "checkbox":
					var checkedValue = $this.data('checked');
					if(data.toString() === checkedValue || data.toString() === "1" || data === true)
						$this.prop('checked', true);
					break;
				case "radio":
					var dataStr = data.toString();
					$this.filter('[value="'+dataStr+'"]').prop('checked', true);
					break;
				default:
					$this.val(data);
					break;
			}
		}
	};

	/**
	 * Populates the children of an object such as a form
	 * by matching data keys with DOM elements that have the
	 * attribute [data-name="key"] or [name="key"]. Uses
	 * $.populate(data) to appropriately fill in the found element.
	 * @param {object} data
	 * @param {boolean} [trigger=true] - whether to call change and input events
	 * @returns {jQuery}
	 */
	$.fn.populateChildren = function(data, trigger = true){
		var $this = $(this);
		$.each(data, function(i ,e){
			var $elInput = $this.find('[name="'+i+'"]');
			var $el = $this.find('[data-name="'+i+'"]');
			if($elInput.length > 0 && $elInput.data('populate') !== false)
				$elInput.populate(e, trigger);
			if($el.length > 0 && $el.data('populate') !== false)
				$el.populate(e, trigger);
		});
		return this;
	};

	/**
	 * Populate an elements attributes by matching
	 * data keys with attributes of the same name.
	 * @param {object} data
	 * @returns {jQuery}
	 */
	$.fn.populateAttr = function(data){
		var $this = $(this);
		$.each(data, function(i, e){
			$this.attr('i', e);
		});
		return this;
	};

	/**
	 * Slide toggle who's first arg is a toggle state
	 * @param {boolean} state - true to slide down
	 * @param {string} [options=""]
	 * @param {function} [cb=null]
	 */
	$.fn.slideToggleState = function(state, options = '', cb = null){
		if(state)
			$(this).slideDown(options, cb);
		else
			$(this).slideUp(options, cb);
		return this;
	};

	/**
	 * Append option(s) to a select
	 * @param {*} arguments - Either an object of key/value pairs, where the key is the
	 * option value and the value is the string within the tags,
	 * or a key and value as two parameters to add one option
	 * @returns {jQuery}
	 */
	$.fn.addToSelect = function(){
		var data = {};

		if(arguments.length > 1)
			data[arguments[0]] = arguments[1];
		else
			data = arguments[0];

		var $this = $(this);
		if($this.is('select')){
			Util.each(data, function(i, e){
				var opt = '<option value="'+i+'">'+e+'</option>';
				$this.append(opt);
			});
		}

		return this;
	};

	/**
	 * Disable/enable an option/set of options based on value attribute
	 * @param {*} [arguments] - Pass a boolean to toggle all options,
	 * pass an array and boolean to toggle some options,
	 * pass a string and boolean to toggle one option
	 * @returns {jQuery}
	 */
	$.fn.toggleOption = function(){
		var $this = $(this);
		if(!$this.is('select'))
			return this;

		var state;
		var value;

		// toggle specific options
		if(arguments.length > 1){
			value = arguments[0];
			if(!$.isArray(value))
				value = [value];

			state = arguments[1];

			for(var i = 0; i < value.length; i++){
				$this.find('option[value="'+value[i]+'"]').prop('disabled', !state);
			}
		}
		// toggle all <option>s
		else {
			state = arguments[0];
			$this.find('option').prop('disabled', !state);
		}

		return this;
	};

})(jQuery);

/**
 * jQuery utility functions
 */
class $Util {

	/**
	 * Attaches all jQuery functions to a
	 * $wrapper property of an object, but
	 * always returns the base object
	 * @param {*} obj - some object that has a $wrapper property
	 * @param {boolean} [override=false] - whether to override any already-named properties
	 * @param {jQuery} obj.$wrapper
	 */
	static jQuerify(obj, override = false) {
		if (!obj.$wrapper)
			throw new ReferenceError('$Util.jQuerify: $wrapper must be a property of the first argument');

		Util.each($Util.jqueryPrototype, function(i, e) {
			// continue if override is false
			if(isDefined(obj[e]) && !override)
				return true;

			obj[e] = function() {
				obj.$wrapper[e](...arguments);
				return obj;
			}
		});
	}

	/**
	 * Convenient wrapper for merging defaults
	 * and options object with jquery deep $.extend
	 * @param {object} defaults - the default settings
	 * @param {object} options - set options
	 * @param {string} [arrayMode] - optional array mode
     */
	static opts(defaults, options, arrayMode){
		if(isDefined(arrayMode)){
			return $.extendext(true, arrayMode, defaults, options);
		}
		else {
			return $.extend(true, defaults, options);
		}
	}
}

$Util.jqueryPrototype = Object.getOwnPropertyNames($.prototype);

// extensions for $.fn.populate
// specificy tag name as object name
// and a prop called populate that is a
// function that takes some data argument
$Util.populate = {};