/* * # Fomantic UI - 2.8.8 * https://github.com/fomantic/Fomantic-UI * http://fomantic-ui.com/ * * Copyright 2022 Contributors * Released under the MIT license * http://opensource.org/licenses/MIT * */ /*! * # Fomantic-UI 2.8.8 - Site * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; $.site = $.fn.site = function(parameters) { var time = new Date().getTime(), performance = [], query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), settings = ( $.isPlainObject(parameters) ) ? $.extend(true, {}, $.site.settings, parameters) : $.extend({}, $.site.settings), namespace = settings.namespace, error = settings.error, moduleNamespace = 'module-' + namespace, $document = $(document), $module = $document, element = this, instance = $module.data(moduleNamespace), module, returnedValue ; module = { initialize: function() { module.instantiate(); }, instantiate: function() { module.verbose('Storing instance of site', module); instance = module; $module .data(moduleNamespace, module) ; }, normalize: function() { module.fix.console(); module.fix.requestAnimationFrame(); }, fix: { console: function() { module.debug('Normalizing window.console'); if (console === undefined || console.log === undefined) { module.verbose('Console not available, normalizing events'); module.disable.console(); } if (typeof console.group == 'undefined' || typeof console.groupEnd == 'undefined' || typeof console.groupCollapsed == 'undefined') { module.verbose('Console group not available, normalizing events'); window.console.group = function() {}; window.console.groupEnd = function() {}; window.console.groupCollapsed = function() {}; } if (typeof console.markTimeline == 'undefined') { module.verbose('Mark timeline not available, normalizing events'); window.console.markTimeline = function() {}; } }, consoleClear: function() { module.debug('Disabling programmatic console clearing'); window.console.clear = function() {}; }, requestAnimationFrame: function() { module.debug('Normalizing requestAnimationFrame'); if(window.requestAnimationFrame === undefined) { module.debug('RequestAnimationFrame not available, normalizing event'); window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { setTimeout(callback, 0); } ; } } }, moduleExists: function(name) { return ($.fn[name] !== undefined && $.fn[name].settings !== undefined); }, enabled: { modules: function(modules) { var enabledModules = [] ; modules = modules || settings.modules; $.each(modules, function(index, name) { if(module.moduleExists(name)) { enabledModules.push(name); } }); return enabledModules; } }, disabled: { modules: function(modules) { var disabledModules = [] ; modules = modules || settings.modules; $.each(modules, function(index, name) { if(!module.moduleExists(name)) { disabledModules.push(name); } }); return disabledModules; } }, change: { setting: function(setting, value, modules, modifyExisting) { modules = (typeof modules === 'string') ? (modules === 'all') ? settings.modules : [modules] : modules || settings.modules ; modifyExisting = (modifyExisting !== undefined) ? modifyExisting : true ; $.each(modules, function(index, name) { var namespace = (module.moduleExists(name)) ? $.fn[name].settings.namespace || false : true, $existingModules ; if(module.moduleExists(name)) { module.verbose('Changing default setting', setting, value, name); $.fn[name].settings[setting] = value; if(modifyExisting && namespace) { $existingModules = $(':data(module-' + namespace + ')'); if($existingModules.length > 0) { module.verbose('Modifying existing settings', $existingModules); $existingModules[name]('setting', setting, value); } } } }); }, settings: function(newSettings, modules, modifyExisting) { modules = (typeof modules === 'string') ? [modules] : modules || settings.modules ; modifyExisting = (modifyExisting !== undefined) ? modifyExisting : true ; $.each(modules, function(index, name) { var $existingModules ; if(module.moduleExists(name)) { module.verbose('Changing default setting', newSettings, name); $.extend(true, $.fn[name].settings, newSettings); if(modifyExisting && namespace) { $existingModules = $(':data(module-' + namespace + ')'); if($existingModules.length > 0) { module.verbose('Modifying existing settings', $existingModules); $existingModules[name]('setting', newSettings); } } } }); } }, enable: { console: function() { module.console(true); }, debug: function(modules, modifyExisting) { modules = modules || settings.modules; module.debug('Enabling debug for modules', modules); module.change.setting('debug', true, modules, modifyExisting); }, verbose: function(modules, modifyExisting) { modules = modules || settings.modules; module.debug('Enabling verbose debug for modules', modules); module.change.setting('verbose', true, modules, modifyExisting); } }, disable: { console: function() { module.console(false); }, debug: function(modules, modifyExisting) { modules = modules || settings.modules; module.debug('Disabling debug for modules', modules); module.change.setting('debug', false, modules, modifyExisting); }, verbose: function(modules, modifyExisting) { modules = modules || settings.modules; module.debug('Disabling verbose debug for modules', modules); module.change.setting('verbose', false, modules, modifyExisting); } }, console: function(enable) { if(enable) { if(instance.cache.console === undefined) { module.error(error.console); return; } module.debug('Restoring console function'); window.console = instance.cache.console; } else { module.debug('Disabling console function'); instance.cache.console = window.console; window.console = { clear : function(){}, error : function(){}, group : function(){}, groupCollapsed : function(){}, groupEnd : function(){}, info : function(){}, log : function(){}, markTimeline : function(){}, warn : function(){} }; } }, destroy: function() { module.verbose('Destroying previous site for', $module); $module .removeData(moduleNamespace) ; }, cache: {}, setting: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, settings, name); } else if(value !== undefined) { settings[name] = value; } else { return settings[name]; } }, internal: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function() { if(settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function() { if(settings.verbose && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function() { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); }, performance: { log: function(message) { var currentTime, executionTime, previousTime ; if(settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Element' : element, 'Name' : message[0], 'Arguments' : [].slice.call(message, 1) || '', 'Execution Time' : executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function(index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if(console.table) { console.table(performance); } else { $.each(performance, function(index, data) { console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function(query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if(typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function(depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { object = object[camelCaseValue]; } else if( object[camelCaseValue] !== undefined ) { found = object[camelCaseValue]; return false; } else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { object = object[value]; } else if( object[value] !== undefined ) { found = object[value]; return false; } else { module.error(error.method, query); return false; } }); } if ( $.isFunction( found ) ) { response = found.apply(context, passedArguments); } else if(found !== undefined) { response = found; } if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if(response !== undefined) { returnedValue = response; } return found; } }; if(methodInvoked) { if(instance === undefined) { module.initialize(); } module.invoke(query); } else { if(instance !== undefined) { module.destroy(); } module.initialize(); } return (returnedValue !== undefined) ? returnedValue : this ; }; $.site.settings = { name : 'Site', namespace : 'site', error : { console : 'Console cannot be restored, most likely it was overwritten outside of module', method : 'The method you called is not defined.' }, debug : false, verbose : false, performance : true, modules: [ 'accordion', 'api', 'calendar', 'checkbox', 'dimmer', 'dropdown', 'embed', 'form', 'modal', 'nag', 'popup', 'slider', 'rating', 'shape', 'sidebar', 'state', 'sticky', 'tab', 'toast', 'transition', 'visibility', 'visit' ], siteNamespace : 'site', namespaceStub : { cache : {}, config : {}, sections : {}, section : {}, utilities : {} } }; // allows for selection of elements with data attributes $.extend($.expr[ ":" ], { data: ($.expr.createPseudo) ? $.expr.createPseudo(function(dataName) { return function(elem) { return !!$.data(elem, dataName); }; }) : function(elem, i, match) { // support: jQuery < 1.8 return !!$.data(elem, match[ 3 ]); } }); })( jQuery, window, document ); /*! * # Fomantic-UI 2.8.8 - Calendar * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) ? self : Function('return this')() ; $.fn.calendar = function(parameters) { var $allModules = $(this), moduleSelector = $allModules.selector || '', time = new Date().getTime(), performance = [], query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), returnedValue, timeGapTable = { '5': {'row': 4, 'column': 3 }, '10': {'row': 3, 'column': 2 }, '15': {'row': 2, 'column': 2 }, '20': {'row': 3, 'column': 1 }, '30': {'row': 2, 'column': 1 } }, numberText = ['','one','two','three','four','five','six','seven','eight'] ; $allModules .each(function () { var settings = ( $.isPlainObject(parameters) ) ? $.extend(true, {}, $.fn.calendar.settings, parameters) : $.extend({}, $.fn.calendar.settings), className = settings.className, namespace = settings.namespace, selector = settings.selector, formatter = settings.formatter, parser = settings.parser, metadata = settings.metadata, timeGap = timeGapTable[settings.minTimeGap], error = settings.error, eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace, $module = $(this), $input = $module.find(selector.input), $container = $module.find(selector.popup), $activator = $module.find(selector.activator), element = this, instance = $module.data(moduleNamespace), isTouch, isTouchDown = false, isInverted = $module.hasClass(className.inverted), focusDateUsedForRange = false, selectionComplete = false, classObserver, module ; module = { initialize: function () { module.debug('Initializing calendar for', element, $module); isTouch = module.get.isTouch(); module.setup.config(); module.setup.popup(); module.setup.inline(); module.setup.input(); module.setup.date(); module.create.calendar(); module.bind.events(); module.observeChanges(); module.instantiate(); }, instantiate: function () { module.verbose('Storing instance of calendar'); instance = module; $module.data(moduleNamespace, instance); }, destroy: function () { module.verbose('Destroying previous calendar for', element); $module.removeData(moduleNamespace); module.unbind.events(); module.disconnect.classObserver(); }, setup: { config: function () { if (module.get.minDate() !== null) { module.set.minDate($module.data(metadata.minDate)); } if (module.get.maxDate() !== null) { module.set.maxDate($module.data(metadata.maxDate)); } module.setting('type', module.get.type()); module.setting('on', settings.on || ($input.length ? 'focus' : 'click')); }, popup: function () { if (settings.inline) { return; } if (!$activator.length) { $activator = $module.children().first(); if (!$activator.length) { return; } } if ($.fn.popup === undefined) { module.error(error.popup); return; } if (!$container.length) { //prepend the popup element to the activator's parent so that it has less chance of messing with //the styling (eg input action button needs to be the last child to have correct border radius) var $activatorParent = $activator.parent(), domPositionFunction = $activatorParent.closest(selector.append).length !== 0 ? 'appendTo' : 'prependTo'; $container = $('
').addClass(className.popup)[domPositionFunction]($activatorParent); } $container.addClass(className.calendar); if(isInverted){ $container.addClass(className.inverted); } var onVisible = function () { module.refreshTooltips(); return settings.onVisible.apply($container, arguments); }; var onHidden = settings.onHidden; if (!$input.length) { //no input, $container has to handle focus/blur $container.attr('tabindex', '0'); onVisible = function () { module.refreshTooltips(); module.focus(); return settings.onVisible.apply($container, arguments); }; onHidden = function () { module.blur(); return settings.onHidden.apply($container, arguments); }; } var onShow = function () { //reset the focus date onShow module.set.focusDate(module.get.date()); module.set.mode(module.get.validatedMode(settings.startMode)); return settings.onShow.apply($container, arguments); }; var on = module.setting('on'); var options = $.extend({}, settings.popupOptions, { popup: $container, on: on, hoverable: on === 'hover', closable: on === 'click', onShow: onShow, onVisible: onVisible, onHide: settings.onHide, onHidden: onHidden }); module.popup(options); }, inline: function () { if ($activator.length && !settings.inline) { return; } settings.inline = true; $container = $('
').addClass(className.calendar).appendTo($module); if (!$input.length) { $container.attr('tabindex', '0'); } }, input: function () { if (settings.touchReadonly && $input.length && isTouch) { $input.prop('readonly', true); } module.check.disabled(); }, date: function () { var date; if (settings.initialDate) { date = parser.date(settings.initialDate, settings); } else if ($module.data(metadata.date) !== undefined) { date = parser.date($module.data(metadata.date), settings); } else if ($input.length) { date = parser.date($input.val(), settings); } module.set.date(date, settings.formatInput, false); module.set.mode(module.get.mode(), false); } }, trigger: { change: function() { var inputElement = $input[0] ; if(inputElement) { var events = document.createEvent('HTMLEvents'); module.verbose('Triggering native change event'); events.initEvent('change', true, false); inputElement.dispatchEvent(events); } } }, create: { calendar: function () { var i, r, c, p, row, cell, pageGrid; var mode = module.get.mode(), today = new Date(), date = module.get.date(), focusDate = module.get.focusDate(), display = module.helper.dateInRange(focusDate || date || settings.initialDate || today) ; if (!focusDate) { focusDate = display; module.set.focusDate(focusDate, false, false); } var isYear = mode === 'year', isMonth = mode === 'month', isDay = mode === 'day', isHour = mode === 'hour', isMinute = mode === 'minute', isTimeOnly = settings.type === 'time' ; var multiMonth = Math.max(settings.multiMonth, 1); var monthOffset = !isDay ? 0 : module.get.monthOffset(); var minute = display.getMinutes(), hour = display.getHours(), day = display.getDate(), startMonth = display.getMonth() + monthOffset, year = display.getFullYear() ; var columns = isDay ? settings.showWeekNumbers ? 8 : 7 : isHour ? 4 : timeGap['column']; var rows = isDay || isHour ? 6 : timeGap['row']; var pages = isDay ? multiMonth : 1; var container = $container; var tooltipPosition = container.hasClass("left") ? "right center" : "left center"; container.empty(); if (pages > 1) { pageGrid = $('
').addClass(className.grid).appendTo(container); } for (p = 0; p < pages; p++) { if (pages > 1) { var pageColumn = $('
').addClass(className.column).appendTo(pageGrid); container = pageColumn; } var month = startMonth + p; var firstMonthDayColumn = (new Date(year, month, 1).getDay() - settings.firstDayOfWeek % 7 + 7) % 7; if (!settings.constantHeight && isDay) { var requiredCells = new Date(year, month + 1, 0).getDate() + firstMonthDayColumn; rows = Math.ceil(requiredCells / 7); } var yearChange = isYear ? 10 : isMonth ? 1 : 0, monthChange = isDay ? 1 : 0, dayChange = isHour || isMinute ? 1 : 0, prevNextDay = isHour || isMinute ? day : 1, prevDate = new Date(year - yearChange, month - monthChange, prevNextDay - dayChange, hour), nextDate = new Date(year + yearChange, month + monthChange, prevNextDay + dayChange, hour), prevLast = isYear ? new Date(Math.ceil(year / 10) * 10 - 9, 0, 0) : isMonth ? new Date(year, 0, 0) : isDay ? new Date(year, month, 0) : new Date(year, month, day, -1), nextFirst = isYear ? new Date(Math.ceil(year / 10) * 10 + 1, 0, 1) : isMonth ? new Date(year + 1, 0, 1) : isDay ? new Date(year, month + 1, 1) : new Date(year, month, day + 1) ; var tempMode = mode; if (isDay && settings.showWeekNumbers){ tempMode += ' andweek'; } var table = $('').addClass(className.table).addClass(tempMode).addClass(numberText[columns] + ' column').appendTo(container); if(isInverted){ table.addClass(className.inverted); } var textColumns = columns; //no header for time-only mode if (!isTimeOnly) { var thead = $('').appendTo(table); row = $('').appendTo(thead); cell = $('').appendTo(thead); if(settings.showWeekNumbers) { cell = $('').appendTo(table); i = isYear ? Math.ceil(year / 10) * 10 - 9 : isDay ? 1 - firstMonthDayColumn : 0; for (r = 0; r < rows; r++) { row = $('').appendTo(tbody); if(isDay && settings.showWeekNumbers){ cell = $('').appendTo(tbody); var todayButton = $('
').attr('colspan', '' + columns).appendTo(row); var headerDate = isYear || isMonth ? new Date(year, 0, 1) : isDay ? new Date(year, month, 1) : new Date(year, month, day, hour, minute); var headerText = $('').addClass(className.link).appendTo(cell); headerText.text(formatter.header(headerDate, mode, settings)); var newMode = isMonth ? (settings.disableYear ? 'day' : 'year') : isDay ? (settings.disableMonth ? 'year' : 'month') : 'day'; headerText.data(metadata.mode, newMode); if (p === 0) { var prev = $('').addClass(className.prev).appendTo(cell); prev.data(metadata.focusDate, prevDate); prev.toggleClass(className.disabledCell, !module.helper.isDateInRange(prevLast, mode)); $('').addClass(className.prevIcon).appendTo(prev); } if (p === pages - 1) { var next = $('').addClass(className.next).appendTo(cell); next.data(metadata.focusDate, nextDate); next.toggleClass(className.disabledCell, !module.helper.isDateInRange(nextFirst, mode)); $('').addClass(className.nextIcon).appendTo(next); } if (isDay) { row = $('
').appendTo(row); cell.text(settings.text.weekNo); cell.addClass(className.weekCell); textColumns--; } for (i = 0; i < textColumns; i++) { cell = $('').appendTo(row); cell.text(formatter.dayColumnHeader((i + settings.firstDayOfWeek) % 7, settings)); } } } var tbody = $('
').appendTo(row); cell.text(module.get.weekOfYear(year,month,i+1-settings.firstDayOfWeek)); cell.addClass(className.weekCell); } for (c = 0; c < textColumns; c++, i++) { var cellDate = isYear ? new Date(i, month, 1, hour, minute) : isMonth ? new Date(year, i, 1, hour, minute) : isDay ? new Date(year, month, i, hour, minute) : isHour ? new Date(year, month, day, i) : new Date(year, month, day, hour, i * settings.minTimeGap); var cellText = isYear ? i : isMonth ? settings.text.monthsShort[i] : isDay ? cellDate.getDate() : formatter.time(cellDate, settings, true); cell = $('').addClass(className.cell).appendTo(row); cell.text(cellText); cell.data(metadata.date, cellDate); var adjacent = isDay && cellDate.getMonth() !== ((month + 12) % 12); var disabled = (!settings.selectAdjacentDays && adjacent) || !module.helper.isDateInRange(cellDate, mode) || settings.isDisabled(cellDate, mode) || module.helper.isDisabled(cellDate, mode) || !module.helper.isEnabled(cellDate, mode); var eventDate; if (disabled) { var disabledDate = module.helper.findDayAsObject(cellDate, mode, settings.disabledDates); if (disabledDate !== null && disabledDate[metadata.message]) { cell.attr("data-tooltip", disabledDate[metadata.message]); cell.attr("data-position", disabledDate[metadata.position] || tooltipPosition); if(disabledDate[metadata.inverted] || (isInverted && disabledDate[metadata.inverted] === undefined)) { cell.attr("data-inverted", ''); } if(disabledDate[metadata.variation]) { cell.attr("data-variation", disabledDate[metadata.variation]); } } } else { eventDate = module.helper.findDayAsObject(cellDate, mode, settings.eventDates); if (eventDate !== null) { cell.addClass(eventDate[metadata.class] || settings.eventClass); if (eventDate[metadata.message]) { cell.attr("data-tooltip", eventDate[metadata.message]); cell.attr("data-position", eventDate[metadata.position] || tooltipPosition); if(eventDate[metadata.inverted] || (isInverted && eventDate[metadata.inverted] === undefined)) { cell.attr("data-inverted", ''); } if(eventDate[metadata.variation]) { cell.attr("data-variation", eventDate[metadata.variation]); } } } } var active = module.helper.dateEqual(cellDate, date, mode); var isToday = module.helper.dateEqual(cellDate, today, mode); cell.toggleClass(className.adjacentCell, adjacent && !eventDate); cell.toggleClass(className.disabledCell, disabled); cell.toggleClass(className.activeCell, active && !(adjacent && disabled)); if (!isHour && !isMinute) { cell.toggleClass(className.todayCell, !adjacent && isToday); } // Allow for external modifications of each cell var cellOptions = { mode: mode, adjacent: adjacent, disabled: disabled, active: active, today: isToday }; formatter.cell(cell, cellDate, cellOptions); if (module.helper.dateEqual(cellDate, focusDate, mode)) { //ensure that the focus date is exactly equal to the cell date //so that, if selected, the correct value is set module.set.focusDate(cellDate, false, false); } } } if (settings.today) { var todayRow = $('
').attr('colspan', '' + columns).addClass(className.today).appendTo(todayRow); todayButton.text(formatter.today(settings)); todayButton.data(metadata.date, today); } module.update.focus(false, table); if(settings.inline){ module.refreshTooltips(); } } } }, update: { focus: function (updateRange, container) { container = container || $container; var mode = module.get.mode(); var date = module.get.date(); var focusDate = module.get.focusDate(); var startDate = module.get.startDate(); var endDate = module.get.endDate(); var rangeDate = (updateRange ? focusDate : null) || date || (!isTouch ? focusDate : null); container.find('td').each(function () { var cell = $(this); var cellDate = cell.data(metadata.date); if (!cellDate) { return; } var disabled = cell.hasClass(className.disabledCell); var active = cell.hasClass(className.activeCell); var adjacent = cell.hasClass(className.adjacentCell); var focused = module.helper.dateEqual(cellDate, focusDate, mode); var inRange = !rangeDate ? false : ((!!startDate && module.helper.isDateInRange(cellDate, mode, startDate, rangeDate)) || (!!endDate && module.helper.isDateInRange(cellDate, mode, rangeDate, endDate))); cell.toggleClass(className.focusCell, focused && (!isTouch || isTouchDown) && (!adjacent || (settings.selectAdjacentDays && adjacent)) && !disabled); if (module.helper.isTodayButton(cell)) { return; } cell.toggleClass(className.rangeCell, inRange && !active && !disabled); }); } }, refresh: function () { module.create.calendar(); }, refreshTooltips: function() { var winWidth = $(window).width(); $container.find('td[data-position]').each(function () { var cell = $(this); var tooltipWidth = window.getComputedStyle(cell[0], ':after').width.replace(/[^0-9\.]/g,''); var tooltipPosition = cell.attr('data-position'); // use a fallback width of 250 (calendar width) for IE/Edge (which return "auto") var calcPosition = (winWidth - cell.width() - (parseInt(tooltipWidth,10) || 250)) > cell.offset().left ? 'right' : 'left'; if(tooltipPosition.indexOf(calcPosition) === -1) { cell.attr('data-position',tooltipPosition.replace(/(left|right)/,calcPosition)); } }); }, bind: { events: function () { module.debug('Binding events'); $container.on('mousedown' + eventNamespace, module.event.mousedown); $container.on('touchstart' + eventNamespace, module.event.mousedown); $container.on('mouseup' + eventNamespace, module.event.mouseup); $container.on('touchend' + eventNamespace, module.event.mouseup); $container.on('mouseover' + eventNamespace, module.event.mouseover); if ($input.length) { $input.on('input' + eventNamespace, module.event.inputChange); $input.on('focus' + eventNamespace, module.event.inputFocus); $input.on('blur' + eventNamespace, module.event.inputBlur); $input.on('keydown' + eventNamespace, module.event.keydown); } else { $container.on('keydown' + eventNamespace, module.event.keydown); } } }, unbind: { events: function () { module.debug('Unbinding events'); $container.off(eventNamespace); if ($input.length) { $input.off(eventNamespace); } } }, event: { mouseover: function (event) { var target = $(event.target); var date = target.data(metadata.date); var mousedown = event.buttons === 1; if (date) { module.set.focusDate(date, false, true, mousedown); } }, mousedown: function (event) { if ($input.length) { //prevent the mousedown on the calendar causing the input to lose focus event.preventDefault(); } isTouchDown = event.type.indexOf('touch') >= 0; var target = $(event.target); var date = target.data(metadata.date); if (date) { module.set.focusDate(date, false, true, true); } }, mouseup: function (event) { //ensure input has focus so that it receives keydown events for calendar navigation module.focus(); event.preventDefault(); event.stopPropagation(); isTouchDown = false; var target = $(event.target); if (target.hasClass("disabled")) { return; } var parent = target.parent(); if (parent.data(metadata.date) || parent.data(metadata.focusDate) || parent.data(metadata.mode)) { //clicked on a child element, switch to parent (used when clicking directly on prev/next icon element) target = parent; } var date = target.data(metadata.date); var focusDate = target.data(metadata.focusDate); var mode = target.data(metadata.mode); if (date && settings.onSelect.call(element, date, module.get.mode()) !== false) { var forceSet = target.hasClass(className.today); module.selectDate(date, forceSet); } else if (focusDate) { module.set.focusDate(focusDate); } else if (mode) { module.set.mode(mode); } }, keydown: function (event) { var keyCode = event.which; if (keyCode === 27 || keyCode === 9) { //esc || tab module.popup('hide'); } if (module.popup('is visible')) { if (keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) { //arrow keys var mode = module.get.mode(); var bigIncrement = mode === 'day' ? 7 : mode === 'hour' ? 4 : mode === 'minute' ? timeGap['column'] : 3; var increment = keyCode === 37 ? -1 : keyCode === 38 ? -bigIncrement : keyCode == 39 ? 1 : bigIncrement; increment *= mode === 'minute' ? settings.minTimeGap : 1; var focusDate = module.get.focusDate() || module.get.date() || new Date(); var year = focusDate.getFullYear() + (mode === 'year' ? increment : 0); var month = focusDate.getMonth() + (mode === 'month' ? increment : 0); var day = focusDate.getDate() + (mode === 'day' ? increment : 0); var hour = focusDate.getHours() + (mode === 'hour' ? increment : 0); var minute = focusDate.getMinutes() + (mode === 'minute' ? increment : 0); var newFocusDate = new Date(year, month, day, hour, minute); if (settings.type === 'time') { newFocusDate = module.helper.mergeDateTime(focusDate, newFocusDate); } if (module.helper.isDateInRange(newFocusDate, mode)) { module.set.focusDate(newFocusDate); } } else if (keyCode === 13) { //enter var mode = module.get.mode(); var date = module.get.focusDate(); if (date && !settings.isDisabled(date, mode) && !module.helper.isDisabled(date, mode) && module.helper.isEnabled(date, mode)) { module.selectDate(date); } //disable form submission: event.preventDefault(); event.stopPropagation(); } } if (keyCode === 38 || keyCode === 40) { //arrow-up || arrow-down event.preventDefault(); //don't scroll module.popup('show'); } }, inputChange: function () { var val = $input.val(); var date = parser.date(val, settings); module.set.date(date, false); }, inputFocus: function () { $container.addClass(className.active); }, inputBlur: function () { $container.removeClass(className.active); if (settings.formatInput) { var date = module.get.date(); var text = formatter.datetime(date, settings); $input.val(text); } if(selectionComplete){ module.trigger.change(); selectionComplete = false; } }, class: { mutation: function(mutations) { mutations.forEach(function(mutation) { if(mutation.attributeName === "class") { module.check.disabled(); } }); } } }, observeChanges: function() { if('MutationObserver' in window) { classObserver = new MutationObserver(module.event.class.mutation); module.debug('Setting up mutation observer', classObserver); module.observe.class(); } }, disconnect: { classObserver: function() { if($input.length && classObserver) { classObserver.disconnect(); } } }, observe: { class: function() { if($input.length && classObserver) { classObserver.observe($module[0], { attributes : true }); } } }, is: { disabled: function() { return $module.hasClass(className.disabled); } }, check: { disabled: function(){ $input.attr('tabindex',module.is.disabled() ? -1 : 0); } }, get: { weekOfYear: function(weekYear,weekMonth,weekDay) { // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm var ms1d = 864e5, // milliseconds in a day ms7d = 7 * ms1d; // milliseconds in a week return function() { // return a closure so constants get calculated only once var DC3 = Date.UTC(weekYear, weekMonth, weekDay + 3) / ms1d, // an Absolute Day Number AWN = Math.floor(DC3 / 7), // an Absolute Week Number Wyr = new Date(AWN * ms7d).getUTCFullYear(); return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1; }(); }, date: function () { return module.helper.sanitiseDate($module.data(metadata.date)) || null; }, inputDate: function() { return $input.val(); }, focusDate: function () { return $module.data(metadata.focusDate) || null; }, startDate: function () { var startModule = module.get.calendarModule(settings.startCalendar); return (startModule ? startModule.get.date() : $module.data(metadata.startDate)) || null; }, endDate: function () { var endModule = module.get.calendarModule(settings.endCalendar); return (endModule ? endModule.get.date() : $module.data(metadata.endDate)) || null; }, minDate: function() { return $module.data(metadata.minDate) || null; }, maxDate: function() { return $module.data(metadata.maxDate) || null; }, monthOffset: function () { return $module.data(metadata.monthOffset) || 0; }, mode: function () { //only returns valid modes for the current settings var mode = $module.data(metadata.mode) || settings.startMode; return module.get.validatedMode(mode); }, validatedMode: function(mode){ var validModes = module.get.validModes(); if ($.inArray(mode, validModes) >= 0) { return mode; } return settings.type === 'time' ? 'hour' : settings.type === 'month' ? 'month' : settings.type === 'year' ? 'year' : 'day'; }, type: function() { return $module.data(metadata.type) || settings.type; }, validModes: function () { var validModes = []; if (settings.type !== 'time') { if (!settings.disableYear || settings.type === 'year') { validModes.push('year'); } if (!(settings.disableMonth || settings.type === 'year') || settings.type === 'month') { validModes.push('month'); } if (settings.type.indexOf('date') >= 0) { validModes.push('day'); } } if (settings.type.indexOf('time') >= 0) { validModes.push('hour'); if (!settings.disableMinute) { validModes.push('minute'); } } return validModes; }, isTouch: function () { try { document.createEvent('TouchEvent'); return true; } catch (e) { return false; } }, calendarModule: function (selector) { if (!selector) { return null; } if (!(selector instanceof $)) { selector = $(selector).first(); } //assume range related calendars are using the same namespace return selector.data(moduleNamespace); } }, set: { date: function (date, updateInput, fireChange) { updateInput = updateInput !== false; fireChange = fireChange !== false; date = module.helper.sanitiseDate(date); date = module.helper.dateInRange(date); var mode = module.get.mode(); var text = formatter.datetime(date, settings); if (fireChange && settings.onBeforeChange.call(element, date, text, mode) === false) { return false; } module.set.focusDate(date); if (settings.isDisabled(date, mode)) { return false; } var endDate = module.get.endDate(); if (!!endDate && !!date && date > endDate) { //selected date is greater than end date in range, so clear end date module.set.endDate(undefined); } module.set.dataKeyValue(metadata.date, date); if (updateInput && $input.length) { $input.val(text); } if (fireChange) { settings.onChange.call(element, date, text, mode); } }, startDate: function (date, refreshCalendar) { date = module.helper.sanitiseDate(date); var startModule = module.get.calendarModule(settings.startCalendar); if (startModule) { startModule.set.date(date); } module.set.dataKeyValue(metadata.startDate, date, refreshCalendar); }, endDate: function (date, refreshCalendar) { date = module.helper.sanitiseDate(date); var endModule = module.get.calendarModule(settings.endCalendar); if (endModule) { endModule.set.date(date); } module.set.dataKeyValue(metadata.endDate, date, refreshCalendar); }, focusDate: function (date, refreshCalendar, updateFocus, updateRange) { date = module.helper.sanitiseDate(date); date = module.helper.dateInRange(date); var isDay = module.get.mode() === 'day'; var oldFocusDate = module.get.focusDate(); if (isDay && date && oldFocusDate) { var yearDelta = date.getFullYear() - oldFocusDate.getFullYear(); var monthDelta = yearDelta * 12 + date.getMonth() - oldFocusDate.getMonth(); if (monthDelta) { var monthOffset = module.get.monthOffset() - monthDelta; module.set.monthOffset(monthOffset, false); } } var changed = module.set.dataKeyValue(metadata.focusDate, date, !!date && refreshCalendar); updateFocus = (updateFocus !== false && changed && refreshCalendar === false) || focusDateUsedForRange != updateRange; focusDateUsedForRange = updateRange; if (updateFocus) { module.update.focus(updateRange); } }, minDate: function (date) { date = module.helper.sanitiseDate(date); if (settings.maxDate !== null && settings.maxDate <= date) { module.verbose('Unable to set minDate variable bigger that maxDate variable', date, settings.maxDate); } else { module.setting('minDate', date); module.set.dataKeyValue(metadata.minDate, date); } }, maxDate: function (date) { date = module.helper.sanitiseDate(date); if (settings.minDate !== null && settings.minDate >= date) { module.verbose('Unable to set maxDate variable lower that minDate variable', date, settings.minDate); } else { module.setting('maxDate', date); module.set.dataKeyValue(metadata.maxDate, date); } }, monthOffset: function (monthOffset, refreshCalendar) { var multiMonth = Math.max(settings.multiMonth, 1); monthOffset = Math.max(1 - multiMonth, Math.min(0, monthOffset)); module.set.dataKeyValue(metadata.monthOffset, monthOffset, refreshCalendar); }, mode: function (mode, refreshCalendar) { module.set.dataKeyValue(metadata.mode, mode, refreshCalendar); }, dataKeyValue: function (key, value, refreshCalendar) { var oldValue = $module.data(key); var equal = oldValue === value || (oldValue <= value && oldValue >= value); //equality test for dates and string objects if (value) { $module.data(key, value); } else { $module.removeData(key); } refreshCalendar = refreshCalendar !== false && !equal; if (refreshCalendar) { module.refresh(); } return !equal; } }, selectDate: function (date, forceSet) { module.verbose('New date selection', date); var mode = module.get.mode(); var complete = forceSet || mode === 'minute' || (settings.disableMinute && mode === 'hour') || (settings.type === 'date' && mode === 'day') || (settings.type === 'month' && mode === 'month') || (settings.type === 'year' && mode === 'year'); if (complete) { var canceled = module.set.date(date) === false; if (!canceled) { selectionComplete = true; if(settings.closable) { module.popup('hide'); //if this is a range calendar, focus the container or input. This will open the popup from its event listeners. var endModule = module.get.calendarModule(settings.endCalendar); if (endModule) { if (endModule.setting('on') !== 'focus') { endModule.popup('show'); } endModule.focus(); } } } } else { var newMode = mode === 'year' ? (!settings.disableMonth ? 'month' : 'day') : mode === 'month' ? 'day' : mode === 'day' ? 'hour' : 'minute'; module.set.mode(newMode); if (mode === 'hour' || (mode === 'day' && module.get.date())) { //the user has chosen enough to consider a valid date/time has been chosen module.set.date(date, true, false); } else { module.set.focusDate(date); } } }, changeDate: function (date) { module.set.date(date); }, clear: function () { module.set.date(undefined); }, popup: function () { return $activator.popup.apply($activator, arguments); }, focus: function () { if ($input.length) { $input.focus(); } else { $container.focus(); } }, blur: function () { if ($input.length) { $input.blur(); } else { $container.blur(); } }, helper: { isDisabled: function(date, mode) { return (mode === 'day' || mode === 'month' || mode === 'year') && ((mode === 'day' && settings.disabledDaysOfWeek.indexOf(date.getDay()) !== -1) || settings.disabledDates.some(function(d){ if(typeof d === 'string') { d = module.helper.sanitiseDate(d); } if (d instanceof Date) { return module.helper.dateEqual(date, d, mode); } if (d !== null && typeof d === 'object') { if (d[metadata.year]) { if (typeof d[metadata.year] === 'number') { return date.getFullYear() == d[metadata.year]; } else if (Array.isArray(d[metadata.year])) { return d[metadata.year].indexOf(date.getFullYear()) > -1; } } else if (d[metadata.month]) { if (typeof d[metadata.month] === 'number') { return date.getMonth() == d[metadata.month]; } else if (Array.isArray(d[metadata.month])) { return d[metadata.month].indexOf(date.getMonth()) > -1; } else if (d[metadata.month] instanceof Date) { var sdate = module.helper.sanitiseDate(d[metadata.month]); return (date.getMonth() == sdate.getMonth()) && (date.getFullYear() == sdate.getFullYear()) } } else if (d[metadata.date] && mode === 'day') { if (d[metadata.date] instanceof Date) { return module.helper.dateEqual(date, module.helper.sanitiseDate(d[metadata.date]), mode); } else if (Array.isArray(d[metadata.date])) { return d[metadata.date].some(function(idate) { return module.helper.dateEqual(date, idate, mode); }); } } } })); }, isEnabled: function(date, mode) { if (mode === 'day') { return settings.enabledDates.length === 0 || settings.enabledDates.some(function(d){ if(typeof d === 'string') { d = module.helper.sanitiseDate(d); } if (d instanceof Date) { return module.helper.dateEqual(date, d, mode); } if (d !== null && typeof d === 'object' && d[metadata.date]) { return module.helper.dateEqual(date, module.helper.sanitiseDate(d[metadata.date]), mode); } }); } else { return true; } }, findDayAsObject: function(date, mode, dates) { if (mode === 'day' || mode === 'month' || mode === 'year') { var d; for (var i = 0; i < dates.length; i++) { d = dates[i]; if(typeof d === 'string') { d = module.helper.sanitiseDate(d); } if (d instanceof Date && module.helper.dateEqual(date, d, mode)) { var dateObject = {}; dateObject[metadata.date] = d; return dateObject; } else if (d !== null && typeof d === 'object') { if (d[metadata.year]) { if (typeof d[metadata.year] === 'number' && date.getFullYear() == d[metadata.year]) { return d; } else if (Array.isArray(d[metadata.year])) { if (d[metadata.year].indexOf(date.getFullYear()) > -1) { return d; } } } else if (d[metadata.month]) { if (typeof d[metadata.month] === 'number' && date.getMonth() == d[metadata.month]) { return d; } else if (Array.isArray(d[metadata.month])) { if (d[metadata.month].indexOf(date.getMonth()) > -1) { return d; } } else if (d[metadata.month] instanceof Date) { var sdate = module.helper.sanitiseDate(d[metadata.month]); if ((date.getMonth() == sdate.getMonth()) && (date.getFullYear() == sdate.getFullYear())) { return d; } } } else if (d[metadata.date] && mode === 'day') { if (d[metadata.date] instanceof Date && module.helper.dateEqual(date, module.helper.sanitiseDate(d[metadata.date]), mode)) { return d; } else if (Array.isArray(d[metadata.date])) { if(d[metadata.date].some(function(idate) { return module.helper.dateEqual(date, idate, mode); })) { return d; } } } } } } return null; }, sanitiseDate: function (date) { if (!(date instanceof Date)) { date = parser.date('' + date, settings); } if (!date || isNaN(date.getTime())) { return null; } return date; }, dateDiff: function (date1, date2, mode) { mode = mode || 'day'; var isTimeOnly = settings.type === 'time'; var isYear = mode === 'year'; var isYearOrMonth = isYear || mode === 'month'; var isMinute = mode === 'minute'; var isHourOrMinute = isMinute || mode === 'hour'; //only care about a minute accuracy of settings.minTimeGap date1 = new Date( isTimeOnly ? 2000 : date1.getFullYear(), isTimeOnly ? 0 : isYear ? 0 : date1.getMonth(), isTimeOnly ? 1 : isYearOrMonth ? 1 : date1.getDate(), !isHourOrMinute ? 0 : date1.getHours(), !isMinute ? 0 : settings.minTimeGap * Math.floor(date1.getMinutes() / settings.minTimeGap)); date2 = new Date( isTimeOnly ? 2000 : date2.getFullYear(), isTimeOnly ? 0 : isYear ? 0 : date2.getMonth(), isTimeOnly ? 1 : isYearOrMonth ? 1 : date2.getDate(), !isHourOrMinute ? 0 : date2.getHours(), !isMinute ? 0 : settings.minTimeGap * Math.floor(date2.getMinutes() / settings.minTimeGap)); return date2.getTime() - date1.getTime(); }, dateEqual: function (date1, date2, mode) { return !!date1 && !!date2 && module.helper.dateDiff(date1, date2, mode) === 0; }, isDateInRange: function (date, mode, minDate, maxDate) { if (!minDate && !maxDate) { var startDate = module.get.startDate(); minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate; maxDate = settings.maxDate; } minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), settings.minTimeGap * Math.ceil(minDate.getMinutes() / settings.minTimeGap)); return !(!date || (minDate && module.helper.dateDiff(date, minDate, mode) > 0) || (maxDate && module.helper.dateDiff(maxDate, date, mode) > 0)); }, dateInRange: function (date, minDate, maxDate) { if (!minDate && !maxDate) { var startDate = module.get.startDate(); minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate; maxDate = settings.maxDate; } minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), settings.minTimeGap * Math.ceil(minDate.getMinutes() / settings.minTimeGap)); var isTimeOnly = settings.type === 'time'; return !date ? date : (minDate && module.helper.dateDiff(date, minDate, 'minute') > 0) ? (isTimeOnly ? module.helper.mergeDateTime(date, minDate) : minDate) : (maxDate && module.helper.dateDiff(maxDate, date, 'minute') > 0) ? (isTimeOnly ? module.helper.mergeDateTime(date, maxDate) : maxDate) : date; }, mergeDateTime: function (date, time) { return (!date || !time) ? time : new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes()); }, isTodayButton: function(element) { return element.text() === settings.text.today; } }, setting: function (name, value) { module.debug('Changing setting', name, value); if ($.isPlainObject(name)) { $.extend(true, settings, name); } else if (value !== undefined) { if ($.isPlainObject(settings[name])) { $.extend(true, settings[name], value); } else { settings[name] = value; } } else { return settings[name]; } }, internal: function (name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function () { if (!settings.silent && settings.debug) { if (settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function () { if (!settings.silent && settings.verbose && settings.debug) { if (settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function () { if (!settings.silent) { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); } }, performance: { log: function (message) { var currentTime, executionTime, previousTime ; if (settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Name': message[0], 'Arguments': [].slice.call(message, 1) || '', 'Element': element, 'Execution Time': executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function () { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function (index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if (moduleSelector) { title += ' \'' + moduleSelector + '\''; } if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if (console.table) { console.table(performance); } else { $.each(performance, function (index, data) { console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function (query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if (typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function (depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) { object = object[camelCaseValue]; } else if (object[camelCaseValue] !== undefined) { found = object[camelCaseValue]; return false; } else if ($.isPlainObject(object[value]) && (depth != maxDepth)) { object = object[value]; } else if (object[value] !== undefined) { found = object[value]; return false; } else { module.error(error.method, query); return false; } }); } if ($.isFunction(found)) { response = found.apply(context, passedArguments); } else if (found !== undefined) { response = found; } if (Array.isArray(returnedValue)) { returnedValue.push(response); } else if (returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if (response !== undefined) { returnedValue = response; } return found; } }; if (methodInvoked) { if (instance === undefined) { module.initialize(); } module.invoke(query); } else { if (instance !== undefined) { instance.invoke('destroy'); } module.initialize(); } }) ; return (returnedValue !== undefined) ? returnedValue : this ; }; $.fn.calendar.settings = { name : 'Calendar', namespace : 'calendar', silent: false, debug: false, verbose: false, performance: false, type : 'datetime', // picker type, can be 'datetime', 'date', 'time', 'month', or 'year' firstDayOfWeek : 0, // day for first day column (0 = Sunday) constantHeight : true, // add rows to shorter months to keep day calendar height consistent (6 rows) today : false, // show a 'today/now' button at the bottom of the calendar closable : true, // close the popup after selecting a date/time monthFirst : true, // month before day when parsing/converting date from/to text touchReadonly : true, // set input to readonly on touch devices inline : false, // create the calendar inline instead of inside a popup on : null, // when to show the popup (defaults to 'focus' for input, 'click' for others) initialDate : null, // date to display initially when no date is selected (null = now) startMode : false, // display mode to start in, can be 'year', 'month', 'day', 'hour', 'minute' (false = 'day') minDate : null, // minimum date/time that can be selected, dates/times before are disabled maxDate : null, // maximum date/time that can be selected, dates/times after are disabled ampm : true, // show am/pm in time mode disableYear : false, // disable year selection mode disableMonth : false, // disable month selection mode disableMinute : false, // disable minute selection mode formatInput : true, // format the input text upon input blur and module creation startCalendar : null, // jquery object or selector for another calendar that represents the start date of a date range endCalendar : null, // jquery object or selector for another calendar that represents the end date of a date range multiMonth : 1, // show multiple months when in 'day' mode minTimeGap : 5, showWeekNumbers : null, // show Number of Week at the very first column of a dayView disabledDates : [], // specific day(s) which won't be selectable and contain additional information. disabledDaysOfWeek : [], // day(s) which won't be selectable(s) (0 = Sunday) enabledDates : [], // specific day(s) which will be selectable, all other days will be disabled eventDates : [], // specific day(s) which will be shown in a different color and using tooltips centuryBreak : 60, // starting short year until 99 where it will be assumed to belong to the last century currentCentury : 2000, // century to be added to 2-digit years (00 to {centuryBreak}-1) selectAdjacentDays : false, // The calendar can show dates from adjacent month. These adjacent month dates can also be made selectable. // popup options ('popup', 'on', 'hoverable', and show/hide callbacks are overridden) popupOptions: { position: 'bottom left', lastResort: 'bottom left', prefer: 'opposite', hideOnScroll: false }, text: { days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], today: 'Today', now: 'Now', am: 'AM', pm: 'PM', weekNo: 'Week' }, formatter: { header: function (date, mode, settings) { return mode === 'year' ? settings.formatter.yearHeader(date, settings) : mode === 'month' ? settings.formatter.monthHeader(date, settings) : mode === 'day' ? settings.formatter.dayHeader(date, settings) : mode === 'hour' ? settings.formatter.hourHeader(date, settings) : settings.formatter.minuteHeader(date, settings); }, yearHeader: function (date, settings) { var decadeYear = Math.ceil(date.getFullYear() / 10) * 10; return (decadeYear - 9) + ' - ' + (decadeYear + 2); }, monthHeader: function (date, settings) { return date.getFullYear(); }, dayHeader: function (date, settings) { var month = settings.text.months[date.getMonth()]; var year = date.getFullYear(); return month + ' ' + year; }, hourHeader: function (date, settings) { return settings.formatter.date(date, settings); }, minuteHeader: function (date, settings) { return settings.formatter.date(date, settings); }, dayColumnHeader: function (day, settings) { return settings.text.days[day]; }, datetime: function (date, settings) { if (!date) { return ''; } var day = settings.type === 'time' ? '' : settings.formatter.date(date, settings); var time = settings.type.indexOf('time') < 0 ? '' : settings.formatter.time(date, settings, false); var separator = settings.type === 'datetime' ? ' ' : ''; return day + separator + time; }, date: function (date, settings) { if (!date) { return ''; } var day = date.getDate(); var month = settings.text.months[date.getMonth()]; var year = date.getFullYear(); return settings.type === 'year' ? year : settings.type === 'month' ? month + ' ' + year : (settings.monthFirst ? month + ' ' + day : day + ' ' + month) + ', ' + year; }, time: function (date, settings, forCalendar) { if (!date) { return ''; } var hour = date.getHours(); var minute = date.getMinutes(); var ampm = ''; if (settings.ampm) { ampm = ' ' + (hour < 12 ? settings.text.am : settings.text.pm); hour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; } return hour + ':' + (minute < 10 ? '0' : '') + minute + ampm; }, today: function (settings) { return settings.type === 'date' ? settings.text.today : settings.text.now; }, cell: function (cell, date, cellOptions) { } }, parser: { date: function (text, settings) { if (text instanceof Date) { return text; } if (!text) { return null; } text = String(text).trim(); if (text.length === 0) { return null; } if(text.match(/^[0-9]{4}[\/\-\.][0-9]{1,2}[\/\-\.][0-9]{1,2}$/)){ text = text.replace(/[\/\-\.]/g,'/') + ' 00:00:00'; } // Reverse date and month in some cases text = settings.monthFirst || !text.match(/^[0-9]{1,2}[\/\-\.]/) ? text : text.replace(/[\/\-\.]/g,'/').replace(/([0-9]+)\/([0-9]+)/,'$2/$1'); var textDate = new Date(text); var numberOnly = text.match(/^[0-9]+$/) !== null; if(!numberOnly && !isNaN(textDate.getDate())) { return textDate; } text = text.toLowerCase(); var i, j, k; var minute = -1, hour = -1, day = -1, month = -1, year = -1; var isAm = undefined; var isTimeOnly = settings.type === 'time'; var isDateOnly = settings.type.indexOf('time') < 0; var words = text.split(settings.regExp.dateWords), word; var numbers = text.split(settings.regExp.dateNumbers), number; var parts; var monthString; if (!isDateOnly) { //am/pm isAm = $.inArray(settings.text.am.toLowerCase(), words) >= 0 ? true : $.inArray(settings.text.pm.toLowerCase(), words) >= 0 ? false : undefined; //time with ':' for (i = 0; i < numbers.length; i++) { number = numbers[i]; if (number.indexOf(':') >= 0) { if (hour < 0 || minute < 0) { parts = number.split(':'); for (k = 0; k < Math.min(2, parts.length); k++) { j = parseInt(parts[k]); if (isNaN(j)) { j = 0; } if (k === 0) { hour = j % 24; } else { minute = j % 60; } } } numbers.splice(i, 1); } } } if (!isTimeOnly) { //textual month for (i = 0; i < words.length; i++) { word = words[i]; if (word.length <= 0) { continue; } for (j = 0; j < settings.text.months.length; j++) { monthString = settings.text.months[j]; monthString = monthString.substring(0, word.length).toLowerCase(); if (monthString === word) { month = j + 1; break; } } if (month >= 0) { break; } } //year > settings.centuryBreak for (i = 0; i < numbers.length; i++) { j = parseInt(numbers[i]); if (isNaN(j)) { continue; } if (j >= settings.centuryBreak && i === numbers.length-1) { if (j <= 99) { j += settings.currentCentury - 100; } year = j; numbers.splice(i, 1); break; } } //numeric month if (month < 0) { for (i = 0; i < numbers.length; i++) { k = i > 1 || settings.monthFirst ? i : i === 1 ? 0 : 1; j = parseInt(numbers[k]); if (isNaN(j)) { continue; } if (1 <= j && j <= 12) { month = j; numbers.splice(k, 1); break; } } } //day for (i = 0; i < numbers.length; i++) { j = parseInt(numbers[i]); if (isNaN(j)) { continue; } if (1 <= j && j <= 31) { day = j; numbers.splice(i, 1); break; } } //year <= settings.centuryBreak if (year < 0) { for (i = numbers.length - 1; i >= 0; i--) { j = parseInt(numbers[i]); if (isNaN(j)) { continue; } if (j <= 99) { j += settings.currentCentury; } year = j; numbers.splice(i, 1); break; } } } if (!isDateOnly) { //hour if (hour < 0) { for (i = 0; i < numbers.length; i++) { j = parseInt(numbers[i]); if (isNaN(j)) { continue; } if (0 <= j && j <= 23) { hour = j; numbers.splice(i, 1); break; } } } //minute if (minute < 0) { for (i = 0; i < numbers.length; i++) { j = parseInt(numbers[i]); if (isNaN(j)) { continue; } if (0 <= j && j <= 59) { minute = j; numbers.splice(i, 1); break; } } } } if (minute < 0 && hour < 0 && day < 0 && month < 0 && year < 0) { return null; } if (minute < 0) { minute = 0; } if (hour < 0) { hour = 0; } if (day < 0) { day = 1; } if (month < 0) { month = 1; } if (year < 0) { year = new Date().getFullYear(); } if (isAm !== undefined) { if (isAm) { if (hour === 12) { hour = 0; } } else if (hour < 12) { hour += 12; } } var date = new Date(year, month - 1, day, hour, minute); if (date.getMonth() !== month - 1 || date.getFullYear() !== year) { //month or year don't match up, switch to last day of the month date = new Date(year, month, 0, hour, minute); } return isNaN(date.getTime()) ? null : date; } }, // callback before date is changed, return false to cancel the change onBeforeChange: function (date, text, mode) { return true; }, // callback when date changes onChange: function (date, text, mode) { }, // callback before show animation, return false to prevent show onShow: function () { }, // callback after show animation onVisible: function () { }, // callback before hide animation, return false to prevent hide onHide: function () { }, // callback after hide animation onHidden: function () { }, // callback before item is selected, return false to prevent selection onSelect: function (date, mode) { }, // is the given date disabled? isDisabled: function (date, mode) { return false; }, selector: { popup: '.ui.popup', input: 'input', activator: 'input', append: '.inline.field,.inline.fields' }, regExp: { dateWords: /[^A-Za-z\u00C0-\u024F]+/g, dateNumbers: /[^\d:]+/g }, error: { popup: 'UI Popup, a required component is not included in this page', method: 'The method you called is not defined.' }, className: { calendar: 'calendar', active: 'active', popup: 'ui popup', grid: 'ui equal width grid', column: 'column', table: 'ui celled center aligned unstackable table', inverted: 'inverted', prev: 'prev link', next: 'next link', prevIcon: 'chevron left icon', nextIcon: 'chevron right icon', link: 'link', cell: 'link', disabledCell: 'disabled', weekCell: 'disabled', adjacentCell: 'adjacent', activeCell: 'active', rangeCell: 'range', focusCell: 'focus', todayCell: 'today', today: 'today link', disabled: 'disabled' }, metadata: { date: 'date', focusDate: 'focusDate', startDate: 'startDate', endDate: 'endDate', minDate: 'minDate', maxDate: 'maxDate', mode: 'mode', type: 'type', monthOffset: 'monthOffset', message: 'message', class: 'class', inverted: 'inverted', variation: 'variation', position: 'position', month: 'month', year: 'year' }, eventClass: 'blue' }; })(jQuery, window, document); /*! * # Fomantic-UI 2.8.8 - Dimmer * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) ? self : Function('return this')() ; $.fn.dimmer = function(parameters) { var $allModules = $(this), time = new Date().getTime(), performance = [], query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), returnedValue ; $allModules .each(function() { var settings = ( $.isPlainObject(parameters) ) ? $.extend(true, {}, $.fn.dimmer.settings, parameters) : $.extend({}, $.fn.dimmer.settings), selector = settings.selector, namespace = settings.namespace, className = settings.className, error = settings.error, eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace, moduleSelector = $allModules.selector || '', clickEvent = ('ontouchstart' in document.documentElement) ? 'touchstart' : 'click', $module = $(this), $dimmer, $dimmable, element = this, instance = $module.data(moduleNamespace), module ; module = { preinitialize: function() { if( module.is.dimmer() ) { $dimmable = $module.parent(); $dimmer = $module; } else { $dimmable = $module; if( module.has.dimmer() ) { if(settings.dimmerName) { $dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName); } else { $dimmer = $dimmable.find(selector.dimmer); } } else { $dimmer = module.create(); } } }, initialize: function() { module.debug('Initializing dimmer', settings); module.bind.events(); module.set.dimmable(); module.instantiate(); }, instantiate: function() { module.verbose('Storing instance of module', module); instance = module; $module .data(moduleNamespace, instance) ; }, destroy: function() { module.verbose('Destroying previous module', $dimmer); module.unbind.events(); module.remove.variation(); $dimmable .off(eventNamespace) ; }, bind: { events: function() { if(settings.on == 'hover') { $dimmable .on('mouseenter' + eventNamespace, module.show) .on('mouseleave' + eventNamespace, module.hide) ; } else if(settings.on == 'click') { $dimmable .on(clickEvent + eventNamespace, module.toggle) ; } if( module.is.page() ) { module.debug('Setting as a page dimmer', $dimmable); module.set.pageDimmer(); } if( module.is.closable() ) { module.verbose('Adding dimmer close event', $dimmer); $dimmable .on(clickEvent + eventNamespace, selector.dimmer, module.event.click) ; } } }, unbind: { events: function() { $module .removeData(moduleNamespace) ; $dimmable .off(eventNamespace) ; } }, event: { click: function(event) { module.verbose('Determining if event occurred on dimmer', event); if( $dimmer.find(event.target).length === 0 || $(event.target).is(selector.content) ) { module.hide(); event.stopImmediatePropagation(); } } }, addContent: function(element) { var $content = $(element) ; module.debug('Add content to dimmer', $content); if($content.parent()[0] !== $dimmer[0]) { $content.detach().appendTo($dimmer); } }, create: function() { var $element = $( settings.template.dimmer(settings) ) ; if(settings.dimmerName) { module.debug('Creating named dimmer', settings.dimmerName); $element.addClass(settings.dimmerName); } $element .appendTo($dimmable) ; return $element; }, show: function(callback) { callback = $.isFunction(callback) ? callback : function(){} ; module.debug('Showing dimmer', $dimmer, settings); module.set.variation(); if( (!module.is.dimmed() || module.is.animating()) && module.is.enabled() ) { module.animate.show(callback); settings.onShow.call(element); settings.onChange.call(element); } else { module.debug('Dimmer is already shown or disabled'); } }, hide: function(callback) { callback = $.isFunction(callback) ? callback : function(){} ; if( module.is.dimmed() || module.is.animating() ) { module.debug('Hiding dimmer', $dimmer); module.animate.hide(callback); settings.onHide.call(element); settings.onChange.call(element); } else { module.debug('Dimmer is not visible'); } }, toggle: function() { module.verbose('Toggling dimmer visibility', $dimmer); if( !module.is.dimmed() ) { module.show(); } else { if ( module.is.closable() ) { module.hide(); } } }, animate: { show: function(callback) { callback = $.isFunction(callback) ? callback : function(){} ; if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { if(settings.useFlex) { module.debug('Using flex dimmer'); module.remove.legacy(); } else { module.debug('Using legacy non-flex dimmer'); module.set.legacy(); } if(settings.opacity !== 'auto') { module.set.opacity(); } $dimmer .transition({ displayType : settings.useFlex ? 'flex' : 'block', animation : (settings.transition.showMethod || settings.transition) + ' in', queue : false, duration : module.get.duration(), useFailSafe : true, onStart : function() { module.set.dimmed(); }, onComplete : function() { module.set.active(); callback(); } }) ; } else { module.verbose('Showing dimmer animation with javascript'); module.set.dimmed(); if(settings.opacity == 'auto') { settings.opacity = 0.8; } $dimmer .stop() .css({ opacity : 0, width : '100%', height : '100%' }) .fadeTo(module.get.duration(), settings.opacity, function() { $dimmer.removeAttr('style'); module.set.active(); callback(); }) ; } }, hide: function(callback) { callback = $.isFunction(callback) ? callback : function(){} ; if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { module.verbose('Hiding dimmer with css'); $dimmer .transition({ displayType : settings.useFlex ? 'flex' : 'block', animation : (settings.transition.hideMethod || settings.transition) + ' out', queue : false, duration : module.get.duration(), useFailSafe : true, onComplete : function() { module.remove.dimmed(); module.remove.variation(); module.remove.active(); callback(); } }) ; } else { module.verbose('Hiding dimmer with javascript'); $dimmer .stop() .fadeOut(module.get.duration(), function() { module.remove.dimmed(); module.remove.active(); $dimmer.removeAttr('style'); callback(); }) ; } } }, get: { dimmer: function() { return $dimmer; }, duration: function() { if( module.is.active() ) { return settings.transition.hideDuration || settings.duration.hide || settings.duration; } else { return settings.transition.showDuration || settings.duration.show || settings.duration; } } }, has: { dimmer: function() { if(settings.dimmerName) { return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0); } else { return ( $module.find(selector.dimmer).length > 0 ); } } }, is: { active: function() { return $dimmer.hasClass(className.active); }, animating: function() { return ( $dimmer.is(':animated') || $dimmer.hasClass(className.animating) ); }, closable: function() { if(settings.closable == 'auto') { if(settings.on == 'hover') { return false; } return true; } return settings.closable; }, dimmer: function() { return $module.hasClass(className.dimmer); }, dimmable: function() { return $module.hasClass(className.dimmable); }, dimmed: function() { return $dimmable.hasClass(className.dimmed); }, disabled: function() { return $dimmable.hasClass(className.disabled); }, enabled: function() { return !module.is.disabled(); }, page: function () { return $dimmable.is('body'); }, pageDimmer: function() { return $dimmer.hasClass(className.pageDimmer); } }, can: { show: function() { return !$dimmer.hasClass(className.disabled); } }, set: { opacity: function(opacity) { var color = $dimmer.css('background-color'), colorArray = color.split(','), isRGB = (colorArray && colorArray.length >= 3) ; opacity = settings.opacity === 0 ? 0 : settings.opacity || opacity; if(isRGB) { colorArray[2] = colorArray[2].replace(')',''); colorArray[3] = opacity + ')'; color = colorArray.join(','); } else { color = 'rgba(0, 0, 0, ' + opacity + ')'; } module.debug('Setting opacity to', opacity); $dimmer.css('background-color', color); }, legacy: function() { $dimmer.addClass(className.legacy); }, active: function() { $dimmer.addClass(className.active); }, dimmable: function() { $dimmable.addClass(className.dimmable); }, dimmed: function() { $dimmable.addClass(className.dimmed); }, pageDimmer: function() { $dimmer.addClass(className.pageDimmer); }, disabled: function() { $dimmer.addClass(className.disabled); }, variation: function(variation) { variation = variation || settings.variation; if(variation) { $dimmer.addClass(variation); } } }, remove: { active: function() { $dimmer .removeClass(className.active) ; }, legacy: function() { $dimmer.removeClass(className.legacy); }, dimmed: function() { $dimmable.removeClass(className.dimmed); }, disabled: function() { $dimmer.removeClass(className.disabled); }, variation: function(variation) { variation = variation || settings.variation; if(variation) { $dimmer.removeClass(variation); } } }, setting: function(name, value) { module.debug('Changing setting', name, value); if( $.isPlainObject(name) ) { $.extend(true, settings, name); } else if(value !== undefined) { if($.isPlainObject(settings[name])) { $.extend(true, settings[name], value); } else { settings[name] = value; } } else { return settings[name]; } }, internal: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function() { if(!settings.silent && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function() { if(!settings.silent && settings.verbose && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function() { if(!settings.silent) { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); } }, performance: { log: function(message) { var currentTime, executionTime, previousTime ; if(settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Name' : message[0], 'Arguments' : [].slice.call(message, 1) || '', 'Element' : element, 'Execution Time' : executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function(index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if(moduleSelector) { title += ' \'' + moduleSelector + '\''; } if($allModules.length > 1) { title += ' ' + '(' + $allModules.length + ')'; } if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if(console.table) { console.table(performance); } else { $.each(performance, function(index, data) { console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function(query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if(typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function(depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { object = object[camelCaseValue]; } else if( object[camelCaseValue] !== undefined ) { found = object[camelCaseValue]; return false; } else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { object = object[value]; } else if( object[value] !== undefined ) { found = object[value]; return false; } else { module.error(error.method, query); return false; } }); } if ( $.isFunction( found ) ) { response = found.apply(context, passedArguments); } else if(found !== undefined) { response = found; } if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if(response !== undefined) { returnedValue = response; } return found; } }; module.preinitialize(); if(methodInvoked) { if(instance === undefined) { module.initialize(); } module.invoke(query); } else { if(instance !== undefined) { instance.invoke('destroy'); } module.initialize(); } }) ; return (returnedValue !== undefined) ? returnedValue : this ; }; $.fn.dimmer.settings = { name : 'Dimmer', namespace : 'dimmer', silent : false, debug : false, verbose : false, performance : true, // whether should use flex layout useFlex : true, // name to distinguish between multiple dimmers in context dimmerName : false, // whether to add a variation type variation : false, // whether to bind close events closable : 'auto', // whether to use css animations useCSS : true, // css animation to use transition : 'fade', // event to bind to on : false, // overriding opacity value opacity : 'auto', // transition durations duration : { show : 500, hide : 500 }, // whether the dynamically created dimmer should have a loader displayLoader: false, loaderText : false, loaderVariation : '', onChange : function(){}, onShow : function(){}, onHide : function(){}, error : { method : 'The method you called is not defined.' }, className : { active : 'active', animating : 'animating', dimmable : 'dimmable', dimmed : 'dimmed', dimmer : 'dimmer', disabled : 'disabled', hide : 'hide', legacy : 'legacy', pageDimmer : 'page', show : 'show', loader : 'ui loader' }, selector: { dimmer : '> .ui.dimmer', content : '.ui.dimmer > .content, .ui.dimmer > .content > .center' }, template: { dimmer: function(settings) { var d = $('
').addClass('ui dimmer'),l; if(settings.displayLoader) { l = $('
') .addClass(settings.className.loader) .addClass(settings.loaderVariation); if(!!settings.loaderText){ l.text(settings.loaderText); l.addClass('text'); } d.append(l); } return d; } } }; })( jQuery, window, document ); /*! * # Fomantic-UI 2.8.8 - Dropdown * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) ? self : Function('return this')() ; $.fn.dropdown = function(parameters) { var $allModules = $(this), $document = $(document), moduleSelector = $allModules.selector || '', hasTouch = ('ontouchstart' in document.documentElement), clickEvent = hasTouch ? 'touchstart' : 'click', time = new Date().getTime(), performance = [], query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), returnedValue ; $allModules .each(function(elementIndex) { var settings = ( $.isPlainObject(parameters) ) ? $.extend(true, {}, $.fn.dropdown.settings, parameters) : $.extend({}, $.fn.dropdown.settings), className = settings.className, message = settings.message, fields = settings.fields, keys = settings.keys, metadata = settings.metadata, namespace = settings.namespace, regExp = settings.regExp, selector = settings.selector, error = settings.error, templates = settings.templates, eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace, $module = $(this), $context = $(settings.context), $text = $module.find(selector.text), $search = $module.find(selector.search), $sizer = $module.find(selector.sizer), $input = $module.find(selector.input), $icon = $module.find(selector.icon), $clear = $module.find(selector.clearIcon), $combo = ($module.prev().find(selector.text).length > 0) ? $module.prev().find(selector.text) : $module.prev(), $menu = $module.children(selector.menu), $item = $menu.find(selector.item), $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), activated = false, itemActivated = false, internalChange = false, iconClicked = false, element = this, focused = false, instance = $module.data(moduleNamespace), selectActionActive, initialLoad, pageLostFocus, willRefocus, elementNamespace, id, selectObserver, menuObserver, classObserver, module ; module = { initialize: function() { module.debug('Initializing dropdown', settings); if( module.is.alreadySetup() ) { module.setup.reference(); } else { if (settings.ignoreDiacritics && !String.prototype.normalize) { settings.ignoreDiacritics = false; module.error(error.noNormalize, element); } module.setup.layout(); if(settings.values) { module.set.initialLoad(); module.change.values(settings.values); module.remove.initialLoad(); } module.refreshData(); module.save.defaults(); module.restore.selected(); module.create.id(); module.bind.events(); module.observeChanges(); module.instantiate(); } }, instantiate: function() { module.verbose('Storing instance of dropdown', module); instance = module; $module .data(moduleNamespace, module) ; }, destroy: function() { module.verbose('Destroying previous dropdown', $module); module.remove.tabbable(); module.remove.active(); $menu.transition('stop all'); $menu.removeClass(className.visible).addClass(className.hidden); $module .off(eventNamespace) .removeData(moduleNamespace) ; $menu .off(eventNamespace) ; $document .off(elementNamespace) ; module.disconnect.menuObserver(); module.disconnect.selectObserver(); module.disconnect.classObserver(); }, observeChanges: function() { if('MutationObserver' in window) { selectObserver = new MutationObserver(module.event.select.mutation); menuObserver = new MutationObserver(module.event.menu.mutation); classObserver = new MutationObserver(module.event.class.mutation); module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); module.observe.select(); module.observe.menu(); module.observe.class(); } }, disconnect: { menuObserver: function() { if(menuObserver) { menuObserver.disconnect(); } }, selectObserver: function() { if(selectObserver) { selectObserver.disconnect(); } }, classObserver: function() { if(classObserver) { classObserver.disconnect(); } } }, observe: { select: function() { if(module.has.input() && selectObserver) { selectObserver.observe($module[0], { childList : true, subtree : true }); } }, menu: function() { if(module.has.menu() && menuObserver) { menuObserver.observe($menu[0], { childList : true, subtree : true }); } }, class: function() { if(module.has.search() && classObserver) { classObserver.observe($module[0], { attributes : true }); } } }, create: { id: function() { id = (Math.random().toString(16) + '000000000').substr(2, 8); elementNamespace = '.' + id; module.verbose('Creating unique id for element', id); }, userChoice: function(values) { var $userChoices, $userChoice, isUserValue, html ; values = values || module.get.userValues(); if(!values) { return false; } values = Array.isArray(values) ? values : [values] ; $.each(values, function(index, value) { if(module.get.item(value) === false) { html = settings.templates.addition( module.add.variables(message.addResult, value) ); $userChoice = $('
') .html(html) .attr('data-' + metadata.value, value) .attr('data-' + metadata.text, value) .addClass(className.addition) .addClass(className.item) ; if(settings.hideAdditions) { $userChoice.addClass(className.hidden); } $userChoices = ($userChoices === undefined) ? $userChoice : $userChoices.add($userChoice) ; module.verbose('Creating user choices for value', value, $userChoice); } }); return $userChoices; }, userLabels: function(value) { var userValues = module.get.userValues() ; if(userValues) { module.debug('Adding user labels', userValues); $.each(userValues, function(index, value) { module.verbose('Adding custom user value'); module.add.label(value, value); }); } }, menu: function() { $menu = $('
') .addClass(className.menu) .appendTo($module) ; }, sizer: function() { $sizer = $('') .addClass(className.sizer) .insertAfter($search) ; } }, search: function(query) { query = (query !== undefined) ? query : module.get.query() ; module.verbose('Searching for query', query); if(settings.fireOnInit === false && module.is.initialLoad()) { module.verbose('Skipping callback on initial load', settings.onSearch); } else if(module.has.minCharacters(query) && settings.onSearch.call(element, query) !== false) { module.filter(query); } else { module.hide(null,true); } }, select: { firstUnfiltered: function() { module.verbose('Selecting first non-filtered element'); module.remove.selectedItem(); $item .not(selector.unselectable) .not(selector.addition + selector.hidden) .eq(0) .addClass(className.selected) ; }, nextAvailable: function($selected) { $selected = $selected.eq(0); var $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), hasNext = ($nextAvailable.length > 0) ; if(hasNext) { module.verbose('Moving selection to', $nextAvailable); $nextAvailable.addClass(className.selected); } else { module.verbose('Moving selection to', $prevAvailable); $prevAvailable.addClass(className.selected); } } }, setup: { api: function() { var apiSettings = { debug : settings.debug, urlData : { value : module.get.value(), query : module.get.query() }, on : false } ; module.verbose('First request, initializing API'); $module .api(apiSettings) ; }, layout: function() { if( $module.is('select') ) { module.setup.select(); module.setup.returnedObject(); } if( !module.has.menu() ) { module.create.menu(); } if ( module.is.clearable() && !module.has.clearItem() ) { module.verbose('Adding clear icon'); $clear = $('') .addClass('remove icon') .insertBefore($text) ; } if( module.is.search() && !module.has.search() ) { module.verbose('Adding search input'); $search = $('') .addClass(className.search) .prop('autocomplete', module.is.chrome() ? 'fomantic-search' : 'off') .insertBefore($text) ; } if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { module.create.sizer(); } if(settings.allowTab) { module.set.tabbable(); } }, select: function() { var selectValues = module.get.selectValues() ; module.debug('Dropdown initialized on a select', selectValues); if( $module.is('select') ) { $input = $module; } // see if select is placed correctly already if($input.parent(selector.dropdown).length > 0) { module.debug('UI dropdown already exists. Creating dropdown menu only'); $module = $input.closest(selector.dropdown); if( !module.has.menu() ) { module.create.menu(); } $menu = $module.children(selector.menu); module.setup.menu(selectValues); } else { module.debug('Creating entire dropdown from select'); $module = $('
') .attr('class', $input.attr('class') ) .addClass(className.selection) .addClass(className.dropdown) .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) .insertBefore($input) ; if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { module.error(error.missingMultiple); $input.prop('multiple', true); } if($input.is('[multiple]')) { module.set.multiple(); } if ($input.prop('disabled')) { module.debug('Disabling dropdown'); $module.addClass(className.disabled); } $input .removeAttr('required') .removeAttr('class') .detach() .prependTo($module) ; } module.refresh(); }, menu: function(values) { $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); $item = $menu.find(selector.item); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, reference: function() { module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); // replace module reference $module = $module.parent(selector.dropdown); instance = $module.data(moduleNamespace); element = $module.get(0); module.refresh(); module.setup.returnedObject(); }, returnedObject: function() { var $firstModules = $allModules.slice(0, elementIndex), $lastModules = $allModules.slice(elementIndex + 1) ; // adjust all modules to use correct reference $allModules = $firstModules.add($module).add($lastModules); } }, refresh: function() { module.refreshSelectors(); module.refreshData(); }, refreshItems: function() { $item = $menu.find(selector.item); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, refreshSelectors: function() { module.verbose('Refreshing selector cache'); $text = $module.find(selector.text); $search = $module.find(selector.search); $input = $module.find(selector.input); $icon = $module.find(selector.icon); $combo = ($module.prev().find(selector.text).length > 0) ? $module.prev().find(selector.text) : $module.prev() ; $menu = $module.children(selector.menu); $item = $menu.find(selector.item); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, refreshData: function() { module.verbose('Refreshing cached metadata'); $item .removeData(metadata.text) .removeData(metadata.value) ; }, clearData: function() { module.verbose('Clearing metadata'); $item .removeData(metadata.text) .removeData(metadata.value) ; $module .removeData(metadata.defaultText) .removeData(metadata.defaultValue) .removeData(metadata.placeholderText) ; }, clearItems: function() { $menu.empty(); module.refreshItems(); }, toggle: function() { module.verbose('Toggling menu visibility'); if( !module.is.active() ) { module.show(); } else { module.hide(); } }, show: function(callback, preventFocus) { callback = $.isFunction(callback) ? callback : function(){} ; if ((focused || iconClicked) && module.is.remote() && module.is.noApiCache()) { module.clearItems(); } if(!module.can.show() && module.is.remote()) { module.debug('No API results retrieved, searching before show'); module.queryRemote(module.get.query(), module.show, [callback, preventFocus]); } if( module.can.show() && !module.is.active() ) { module.debug('Showing dropdown'); if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { module.remove.message(); } if(module.is.allFiltered()) { return true; } if(settings.onShow.call(element) !== false) { module.animate.show(function() { if( module.can.click() ) { module.bind.intent(); } if(module.has.search() && !preventFocus) { module.focusSearch(); } module.set.visible(); callback.call(element); }); } } }, hide: function(callback, preventBlur) { callback = $.isFunction(callback) ? callback : function(){} ; if( module.is.active() && !module.is.animatingOutward() ) { module.debug('Hiding dropdown'); if(settings.onHide.call(element) !== false) { module.animate.hide(function() { module.remove.visible(); // hidding search focus if ( module.is.focusedOnSearch() && preventBlur !== true ) { $search.blur(); } callback.call(element); }); } } else if( module.can.click() ) { module.unbind.intent(); } iconClicked = false; focused = false; }, hideOthers: function() { module.verbose('Finding other dropdowns to hide'); $allModules .not($module) .has(selector.menu + '.' + className.visible) .dropdown('hide') ; }, hideMenu: function() { module.verbose('Hiding menu instantaneously'); module.remove.active(); module.remove.visible(); $menu.transition('hide'); }, hideSubMenus: function() { var $subMenus = $menu.children(selector.item).find(selector.menu) ; module.verbose('Hiding sub menus', $subMenus); $subMenus.transition('hide'); }, bind: { events: function() { module.bind.keyboardEvents(); module.bind.inputEvents(); module.bind.mouseEvents(); }, keyboardEvents: function() { module.verbose('Binding keyboard events'); $module .on('keydown' + eventNamespace, module.event.keydown) ; if( module.has.search() ) { $module .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) ; } if( module.is.multiple() ) { $document .on('keydown' + elementNamespace, module.event.document.keydown) ; } }, inputEvents: function() { module.verbose('Binding input change events'); $module .on('change' + eventNamespace, selector.input, module.event.change) ; }, mouseEvents: function() { module.verbose('Binding mouse events'); if(module.is.multiple()) { $module .on(clickEvent + eventNamespace, selector.label, module.event.label.click) .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) ; } if( module.is.searchSelection() ) { $module .on('mousedown' + eventNamespace, module.event.mousedown) .on('mouseup' + eventNamespace, module.event.mouseup) .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) .on('focus' + eventNamespace, selector.search, module.event.search.focus) .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) .on('blur' + eventNamespace, selector.search, module.event.search.blur) .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) ; if(module.is.multiple()) { $module .on(clickEvent + eventNamespace, module.event.click) .on(clickEvent + eventNamespace, module.event.search.focus) ; } } else { if(settings.on == 'click') { $module .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) .on(clickEvent + eventNamespace, module.event.test.toggle) ; } else if(settings.on == 'hover') { $module .on('mouseenter' + eventNamespace, module.delay.show) .on('mouseleave' + eventNamespace, module.delay.hide) ; } else { $module .on(settings.on + eventNamespace, module.toggle) ; } $module .on('mousedown' + eventNamespace, module.event.mousedown) .on('mouseup' + eventNamespace, module.event.mouseup) .on('focus' + eventNamespace, module.event.focus) .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) ; if(module.has.menuSearch() ) { $module .on('blur' + eventNamespace, selector.search, module.event.search.blur) ; } else { $module .on('blur' + eventNamespace, module.event.blur) ; } } $menu .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) .on('click' + eventNamespace, selector.item, module.event.item.click) ; }, intent: function() { module.verbose('Binding hide intent event to document'); if(hasTouch) { $document .on('touchstart' + elementNamespace, module.event.test.touch) .on('touchmove' + elementNamespace, module.event.test.touch) ; } $document .on(clickEvent + elementNamespace, module.event.test.hide) ; } }, unbind: { intent: function() { module.verbose('Removing hide intent event from document'); if(hasTouch) { $document .off('touchstart' + elementNamespace) .off('touchmove' + elementNamespace) ; } $document .off(clickEvent + elementNamespace) ; } }, filter: function(query) { var searchTerm = (query !== undefined) ? query : module.get.query(), afterFiltered = function() { if(module.is.multiple()) { module.filterActive(); } if(query || (!query && module.get.activeItem().length == 0)) { module.select.firstUnfiltered(); } if( module.has.allResultsFiltered() ) { if( settings.onNoResults.call(element, searchTerm) ) { if(settings.allowAdditions) { if(settings.hideAdditions) { module.verbose('User addition with no menu, setting empty style'); module.set.empty(); module.hideMenu(); } } else { module.verbose('All items filtered, showing message', searchTerm); module.add.message(message.noResults); } } else { module.verbose('All items filtered, hiding dropdown', searchTerm); module.hideMenu(); } } else { module.remove.empty(); module.remove.message(); } if(settings.allowAdditions) { module.add.userSuggestion(module.escape.htmlEntities(query)); } if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { module.show(); } } ; if(settings.useLabels && module.has.maxSelections()) { return; } if(settings.apiSettings) { if( module.can.useAPI() ) { module.queryRemote(searchTerm, function() { if(settings.filterRemoteData) { module.filterItems(searchTerm); } var preSelected = $input.val(); if(!Array.isArray(preSelected)) { preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; } if (module.is.multiple()) { $.each(preSelected,function(index,value){ $item.filter('[data-value="'+value+'"]') .addClass(className.filtered) ; }); } module.focusSearch(true); afterFiltered(); }); } else { module.error(error.noAPI); } } else { module.filterItems(searchTerm); afterFiltered(); } }, queryRemote: function(query, callback, callbackParameters) { if(!Array.isArray(callbackParameters)){ callbackParameters = [callbackParameters]; } var apiSettings = { errorDuration : false, cache : 'local', throttle : settings.throttle, urlData : { query: query }, onError: function() { module.add.message(message.serverError); iconClicked = false; focused = false; callback.apply(null, callbackParameters); }, onFailure: function() { module.add.message(message.serverError); iconClicked = false; focused = false; callback.apply(null, callbackParameters); }, onSuccess : function(response) { var values = response[fields.remoteValues] ; if (!Array.isArray(values)){ values = []; } module.remove.message(); var menuConfig = {}; menuConfig[fields.values] = values; module.setup.menu(menuConfig); if(values.length===0 && !settings.allowAdditions) { module.add.message(message.noResults); } else { var value = module.is.multiple() ? module.get.values() : module.get.value(); if (value !== '') { module.verbose('Value(s) present after click icon, select value(s) in items'); module.set.selected(value, null, null, true); } } iconClicked = false; focused = false; callback.apply(null, callbackParameters); } } ; if( !$module.api('get request') ) { module.setup.api(); } apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); $module .api('setting', apiSettings) .api('query') ; }, filterItems: function(query) { var searchTerm = module.remove.diacritics(query !== undefined ? query : module.get.query() ), results = null, escapedTerm = module.escape.string(searchTerm), regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) ; // avoid loop if we're matching nothing if( module.has.query() ) { results = []; module.verbose('Searching for matching values', searchTerm); $item .each(function(){ var $choice = $(this), text, value ; if($choice.hasClass(className.unfilterable)) { results.push(this); return true; } if(settings.match === 'both' || settings.match === 'text') { text = module.remove.diacritics(String(module.get.choiceText($choice, false))); if(text.search(beginsWithRegExp) !== -1) { results.push(this); return true; } else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { results.push(this); return true; } else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { results.push(this); return true; } } if(settings.match === 'both' || settings.match === 'value') { value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); if(value.search(beginsWithRegExp) !== -1) { results.push(this); return true; } else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { results.push(this); return true; } else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { results.push(this); return true; } } }) ; } module.debug('Showing only matched items', searchTerm); module.remove.filteredItem(); if(results) { $item .not(results) .addClass(className.filtered) ; } if(!module.has.query()) { $divider .removeClass(className.hidden); } else if(settings.hideDividers === true) { $divider .addClass(className.hidden); } else if(settings.hideDividers === 'empty') { $divider .removeClass(className.hidden) .filter(function() { // First find the last divider in this divider group // Dividers which are direct siblings are considered a group var lastDivider = $(this).nextUntil(selector.item); return (lastDivider.length ? lastDivider : $(this)) // Count all non-filtered items until the next divider (or end of the dropdown) .nextUntil(selector.divider) .filter(selector.item + ":not(." + className.filtered + ")") // Hide divider if no items are found .length === 0; }) .addClass(className.hidden); } }, fuzzySearch: function(query, term) { var termLength = term.length, queryLength = query.length ; query = (settings.ignoreSearchCase ? query.toLowerCase() : query); term = (settings.ignoreSearchCase ? term.toLowerCase() : term); if(queryLength > termLength) { return false; } if(queryLength === termLength) { return (query === term); } search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { var queryCharacter = query.charCodeAt(characterIndex) ; while(nextCharacterIndex < termLength) { if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { continue search; } } return false; } return true; }, exactSearch: function (query, term) { query = (settings.ignoreSearchCase ? query.toLowerCase() : query); term = (settings.ignoreSearchCase ? term.toLowerCase() : term); return term.indexOf(query) > -1; }, filterActive: function() { if(settings.useLabels) { $item.filter('.' + className.active) .addClass(className.filtered) ; } }, focusSearch: function(skipHandler) { if( module.has.search() && !module.is.focusedOnSearch() ) { if(skipHandler) { $module.off('focus' + eventNamespace, selector.search); $search.focus(); $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); } else { $search.focus(); } } }, blurSearch: function() { if( module.has.search() ) { $search.blur(); } }, forceSelection: function() { var $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), $selectedItem = ($currentlySelected.length > 0) ? $currentlySelected : $activeItem, hasSelected = ($selectedItem.length > 0) ; if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { module.debug('Forcing partial selection to selected item', $selectedItem); module.event.item.click.call($selectedItem, {}, true); } else { module.remove.searchTerm(); } }, change: { values: function(values) { if(!settings.allowAdditions) { module.clear(); } module.debug('Creating dropdown with specified values', values); var menuConfig = {}; menuConfig[fields.values] = values; module.setup.menu(menuConfig); $.each(values, function(index, item) { if(item.selected == true) { module.debug('Setting initial selection to', item[fields.value]); module.set.selected(item[fields.value]); if(!module.is.multiple()) { return false; } } }); if(module.has.selectInput()) { module.disconnect.selectObserver(); $input.html(''); $input.append(''); $.each(values, function(index, item) { var value = settings.templates.deQuote(item[fields.value]), name = settings.templates.escape( item[fields.name] || '', settings.preserveHTML ) ; $input.append(''); }); module.observe.select(); } } }, event: { change: function() { if(!internalChange) { module.debug('Input changed, updating selection'); module.set.selected(); } }, focus: function() { if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { focused = true; module.show(); } }, blur: function(event) { pageLostFocus = (document.activeElement === this); if(!activated && !pageLostFocus) { module.remove.activeLabel(); module.hide(); } }, mousedown: function() { if(module.is.searchSelection()) { // prevent menu hiding on immediate re-focus willRefocus = true; } else { // prevents focus callback from occurring on mousedown activated = true; } }, mouseup: function() { if(module.is.searchSelection()) { // prevent menu hiding on immediate re-focus willRefocus = false; } else { activated = false; } }, click: function(event) { var $target = $(event.target) ; // focus search if($target.is($module)) { if(!module.is.focusedOnSearch()) { module.focusSearch(); } else { module.show(); } } }, search: { focus: function(event) { activated = true; if(module.is.multiple()) { module.remove.activeLabel(); } if(!focused && !module.is.active() && (settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin'))) { focused = true; module.search(); } }, blur: function(event) { pageLostFocus = (document.activeElement === this); if(module.is.searchSelection() && !willRefocus) { if(!itemActivated && !pageLostFocus) { if(settings.forceSelection) { module.forceSelection(); } else if(!settings.allowAdditions){ module.remove.searchTerm(); } module.hide(); } } willRefocus = false; } }, clearIcon: { click: function(event) { module.clear(); if(module.is.searchSelection()) { module.remove.searchTerm(); } module.hide(); event.stopPropagation(); } }, icon: { click: function(event) { iconClicked=true; if(module.has.search()) { if(!module.is.active()) { if(settings.showOnFocus){ module.focusSearch(); } else { module.toggle(); } } else { module.blurSearch(); } } else { module.toggle(); } event.stopPropagation(); } }, text: { focus: function(event) { activated = true; module.focusSearch(); } }, input: function(event) { if(module.is.multiple() || module.is.searchSelection()) { module.set.filtered(); } clearTimeout(module.timer); module.timer = setTimeout(module.search, settings.delay.search); }, label: { click: function(event) { var $label = $(this), $labels = $module.find(selector.label), $activeLabels = $labels.filter('.' + className.active), $nextActive = $label.nextAll('.' + className.active), $prevActive = $label.prevAll('.' + className.active), $range = ($nextActive.length > 0) ? $label.nextUntil($nextActive).add($activeLabels).add($label) : $label.prevUntil($prevActive).add($activeLabels).add($label) ; if(event.shiftKey) { $activeLabels.removeClass(className.active); $range.addClass(className.active); } else if(event.ctrlKey) { $label.toggleClass(className.active); } else { $activeLabels.removeClass(className.active); $label.addClass(className.active); } settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); event.stopPropagation(); } }, remove: { click: function(event) { var $label = $(this).parent() ; if( $label.hasClass(className.active) ) { // remove all selected labels module.remove.activeLabels(); } else { // remove this label only module.remove.activeLabels( $label ); } event.stopPropagation(); } }, test: { toggle: function(event) { var toggleBehavior = (module.is.multiple()) ? module.show : module.toggle ; if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { return; } if (!module.is.multiple() || (module.is.multiple() && !module.is.active())) { focused = true; } if( module.determine.eventOnElement(event, toggleBehavior) ) { event.preventDefault(); } }, touch: function(event) { module.determine.eventOnElement(event, function() { if(event.type == 'touchstart') { module.timer = setTimeout(function() { module.hide(); }, settings.delay.touch); } else if(event.type == 'touchmove') { clearTimeout(module.timer); } }); event.stopPropagation(); }, hide: function(event) { if(module.determine.eventInModule(event, module.hide)){ if(element.id && $(event.target).attr('for') === element.id){ event.preventDefault(); } } } }, class: { mutation: function(mutations) { mutations.forEach(function(mutation) { if(mutation.attributeName === "class") { module.check.disabled(); } }); } }, select: { mutation: function(mutations) { module.debug(' removing selected option', removedValue); newValue = module.remove.arrayValue(removedValue, values); module.remove.optionValue(removedValue); } else { module.verbose('Removing from delimited values', removedValue); newValue = module.remove.arrayValue(removedValue, values); newValue = newValue.join(settings.delimiter); } if(settings.fireOnInit === false && module.is.initialLoad()) { module.verbose('No callback on initial load', settings.onRemove); } else { settings.onRemove.call(element, removedValue, removedText, $removedItem); } module.set.value(newValue, removedText, $removedItem, preventChangeTrigger); module.check.maxSelections(); }, arrayValue: function(removedValue, values) { if( !Array.isArray(values) ) { values = [values]; } values = $.grep(values, function(value){ return (removedValue != value); }); module.verbose('Removed value from delimited string', removedValue, values); return values; }, label: function(value, shouldAnimate) { var escapedValue = module.escape.value(value), $labels = $module.find(selector.label), $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? escapedValue.toLowerCase() : escapedValue) +'"]') ; module.verbose('Removing label', $removedLabel); $removedLabel.remove(); }, activeLabels: function($activeLabels) { $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); module.verbose('Removing active label selections', $activeLabels); module.remove.labels($activeLabels); }, labels: function($labels, preventChangeTrigger) { $labels = $labels || $module.find(selector.label); module.verbose('Removing labels', $labels); $labels .each(function(){ var $label = $(this), value = $label.data(metadata.value), stringValue = (value !== undefined) ? String(value) : value, isUserValue = module.is.userValue(stringValue) ; if(settings.onLabelRemove.call($label, value) === false) { module.debug('Label remove callback cancelled removal'); return; } module.remove.message(); if(isUserValue) { module.remove.value(stringValue, stringValue, module.get.item(stringValue), preventChangeTrigger); module.remove.label(stringValue); } else { // selected will also remove label module.remove.selected(stringValue, false, preventChangeTrigger); } }) ; }, tabbable: function() { if( module.is.searchSelection() ) { module.debug('Searchable dropdown initialized'); $search .removeAttr('tabindex') ; $menu .removeAttr('tabindex') ; } else { module.debug('Simple selection dropdown initialized'); $module .removeAttr('tabindex') ; $menu .removeAttr('tabindex') ; } }, diacritics: function(text) { return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; } }, has: { menuSearch: function() { return (module.has.search() && $search.closest($menu).length > 0); }, clearItem: function() { return ($clear.length > 0); }, search: function() { return ($search.length > 0); }, sizer: function() { return ($sizer.length > 0); }, selectInput: function() { return ( $input.is('select') ); }, minCharacters: function(searchTerm) { if(settings.minCharacters && !iconClicked) { searchTerm = (searchTerm !== undefined) ? String(searchTerm) : String(module.get.query()) ; return (searchTerm.length >= settings.minCharacters); } iconClicked=false; return true; }, firstLetter: function($item, letter) { var text, firstLetter ; if(!$item || $item.length === 0 || typeof letter !== 'string') { return false; } text = module.get.choiceText($item, false); letter = letter.toLowerCase(); firstLetter = String(text).charAt(0).toLowerCase(); return (letter == firstLetter); }, input: function() { return ($input.length > 0); }, items: function() { return ($item.length > 0); }, menu: function() { return ($menu.length > 0); }, subMenu: function($currentMenu) { return ($currentMenu || $menu).find(selector.menu).length > 0; }, message: function() { return ($menu.children(selector.message).length !== 0); }, label: function(value) { var escapedValue = module.escape.value(value), $labels = $module.find(selector.label) ; if(settings.ignoreCase) { escapedValue = escapedValue.toLowerCase(); } return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); }, maxSelections: function() { return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); }, allResultsFiltered: function() { var $normalResults = $item.not(selector.addition) ; return ($normalResults.filter(selector.unselectable).length === $normalResults.length); }, userSuggestion: function() { return ($menu.children(selector.addition).length > 0); }, query: function() { return (module.get.query() !== ''); }, value: function(value) { return (settings.ignoreCase) ? module.has.valueIgnoringCase(value) : module.has.valueMatchingCase(value) ; }, valueMatchingCase: function(value) { var values = module.get.values(true), hasValue = Array.isArray(values) ? values && ($.inArray(value, values) !== -1) : (values == value) ; return (hasValue) ? true : false ; }, valueIgnoringCase: function(value) { var values = module.get.values(true), hasValue = false ; if(!Array.isArray(values)) { values = [values]; } $.each(values, function(index, existingValue) { if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { hasValue = true; return false; } }); return hasValue; } }, is: { active: function() { return $module.hasClass(className.active); }, animatingInward: function() { return $menu.transition('is inward'); }, animatingOutward: function() { return $menu.transition('is outward'); }, bubbledLabelClick: function(event) { return $(event.target).is('select, input') && $module.closest('label').length > 0; }, bubbledIconClick: function(event) { return $(event.target).closest($icon).length > 0; }, chrome: function() { return !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime); }, alreadySetup: function() { return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); }, animating: function($subMenu) { return ($subMenu) ? $subMenu.transition && $subMenu.transition('is animating') : $menu.transition && $menu.transition('is animating') ; }, leftward: function($subMenu) { var $selectedMenu = $subMenu || $menu; return $selectedMenu.hasClass(className.leftward); }, clearable: function() { return ($module.hasClass(className.clearable) || settings.clearable); }, disabled: function() { return $module.hasClass(className.disabled); }, focused: function() { return (document.activeElement === $module[0]); }, focusedOnSearch: function() { return (document.activeElement === $search[0]); }, allFiltered: function() { return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); }, hidden: function($subMenu) { return !module.is.visible($subMenu); }, initialLoad: function() { return initialLoad; }, inObject: function(needle, object) { var found = false ; $.each(object, function(index, property) { if(property == needle) { found = true; return true; } }); return found; }, multiple: function() { return $module.hasClass(className.multiple); }, remote: function() { return settings.apiSettings && module.can.useAPI(); }, noApiCache: function() { return settings.apiSettings && !settings.apiSettings.cache }, single: function() { return !module.is.multiple(); }, selectMutation: function(mutations) { var selectChanged = false ; $.each(mutations, function(index, mutation) { if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { selectChanged = true; return false; } }); return selectChanged; }, search: function() { return $module.hasClass(className.search); }, searchSelection: function() { return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); }, selection: function() { return $module.hasClass(className.selection); }, userValue: function(value) { return ($.inArray(value, module.get.userValues()) !== -1); }, upward: function($menu) { var $element = $menu || $module; return $element.hasClass(className.upward); }, visible: function($subMenu) { return ($subMenu) ? $subMenu.hasClass(className.visible) : $menu.hasClass(className.visible) ; }, verticallyScrollableContext: function() { var overflowY = ($context.get(0) !== window) ? $context.css('overflow-y') : false ; return (overflowY == 'auto' || overflowY == 'scroll'); }, horizontallyScrollableContext: function() { var overflowX = ($context.get(0) !== window) ? $context.css('overflow-X') : false ; return (overflowX == 'auto' || overflowX == 'scroll'); } }, can: { activate: function($item) { if(settings.useLabels) { return true; } if(!module.has.maxSelections()) { return true; } if(module.has.maxSelections() && $item.hasClass(className.active)) { return true; } return false; }, openDownward: function($subMenu) { var $currentMenu = $subMenu || $menu, canOpenDownward = true, onScreen = {}, calculations ; $currentMenu .addClass(className.loading) ; calculations = { context: { offset : ($context.get(0) === window) ? { top: 0, left: 0} : $context.offset(), scrollTop : $context.scrollTop(), height : $context.outerHeight() }, menu : { offset: $currentMenu.offset(), height: $currentMenu.outerHeight() } }; if(module.is.verticallyScrollableContext()) { calculations.menu.offset.top += calculations.context.scrollTop; } if(module.has.subMenu($currentMenu)) { calculations.menu.height += $currentMenu.find(selector.menu).first().outerHeight(); } onScreen = { above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height }; if(onScreen.below) { module.verbose('Dropdown can fit in context downward', onScreen); canOpenDownward = true; } else if(!onScreen.below && !onScreen.above) { module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); canOpenDownward = true; } else { module.verbose('Dropdown cannot fit below, opening upward', onScreen); canOpenDownward = false; } $currentMenu.removeClass(className.loading); return canOpenDownward; }, openRightward: function($subMenu) { var $currentMenu = $subMenu || $menu, canOpenRightward = true, isOffscreenRight = false, calculations ; $currentMenu .addClass(className.loading) ; calculations = { context: { offset : ($context.get(0) === window) ? { top: 0, left: 0} : $context.offset(), scrollLeft : $context.scrollLeft(), width : $context.outerWidth() }, menu: { offset : $currentMenu.offset(), width : $currentMenu.outerWidth() } }; if(module.is.horizontallyScrollableContext()) { calculations.menu.offset.left += calculations.context.scrollLeft; } isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); if(isOffscreenRight) { module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); canOpenRightward = false; } $currentMenu.removeClass(className.loading); return canOpenRightward; }, click: function() { return (hasTouch || settings.on == 'click'); }, extendSelect: function() { return settings.allowAdditions || settings.apiSettings; }, show: function() { return !module.is.disabled() && (module.has.items() || module.has.message()); }, useAPI: function() { return $.fn.api !== undefined; } }, animate: { show: function(callback, $subMenu) { var $currentMenu = $subMenu || $menu, start = ($subMenu) ? function() {} : function() { module.hideSubMenus(); module.hideOthers(); module.set.active(); }, transition ; callback = $.isFunction(callback) ? callback : function(){} ; module.verbose('Doing menu show animation', $currentMenu); module.set.direction($subMenu); transition = settings.transition.showMethod || module.get.transition($subMenu); if( module.is.selection() ) { module.set.scrollPosition(module.get.selectedItem(), true); } if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { if(transition === 'none') { start(); $currentMenu.transition({ displayType: module.get.displayType() }).transition('show'); callback.call(element); } else if($.fn.transition !== undefined && $module.transition('is supported')) { $currentMenu .transition({ animation : transition + ' in', debug : settings.debug, verbose : settings.verbose, duration : settings.transition.showDuration || settings.duration, queue : true, onStart : start, displayType: module.get.displayType(), onComplete : function() { callback.call(element); } }) ; } else { module.error(error.noTransition, transition); } } }, hide: function(callback, $subMenu) { var $currentMenu = $subMenu || $menu, start = ($subMenu) ? function() {} : function() { if( module.can.click() ) { module.unbind.intent(); } module.remove.active(); }, transition = settings.transition.hideMethod || module.get.transition($subMenu) ; callback = $.isFunction(callback) ? callback : function(){} ; if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { module.verbose('Doing menu hide animation', $currentMenu); if(transition === 'none') { start(); $currentMenu.transition({ displayType: module.get.displayType() }).transition('hide'); callback.call(element); } else if($.fn.transition !== undefined && $module.transition('is supported')) { $currentMenu .transition({ animation : transition + ' out', duration : settings.transition.hideDuration || settings.duration, debug : settings.debug, verbose : settings.verbose, queue : false, onStart : start, displayType: module.get.displayType(), onComplete : function() { callback.call(element); } }) ; } else { module.error(error.transition); } } } }, hideAndClear: function() { module.remove.searchTerm(); if( module.has.maxSelections() ) { return; } if(module.has.search()) { module.hide(function() { module.remove.filteredItem(); }); } else { module.hide(); } }, delay: { show: function() { module.verbose('Delaying show event to ensure user intent'); clearTimeout(module.timer); module.timer = setTimeout(module.show, settings.delay.show); }, hide: function() { module.verbose('Delaying hide event to ensure user intent'); clearTimeout(module.timer); module.timer = setTimeout(module.hide, settings.delay.hide); } }, escape: { value: function(value) { var multipleValues = Array.isArray(value), stringValue = (typeof value === 'string'), isUnparsable = (!stringValue && !multipleValues), hasQuotes = (stringValue && value.search(regExp.quote) !== -1), values = [] ; if(isUnparsable || !hasQuotes) { return value; } module.debug('Encoding quote values for use in select', value); if(multipleValues) { $.each(value, function(index, value){ values.push(value.replace(regExp.quote, '"')); }); return values; } return value.replace(regExp.quote, '"'); }, string: function(text) { text = String(text); return text.replace(regExp.escape, '\\$&'); }, htmlEntities: function(string) { var badChars = /[<>"'`]/g, shouldEscape = /[&<>"'`]/, escape = { "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }, escapedChar = function(chr) { return escape[chr]; } ; if(shouldEscape.test(string)) { string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); return string.replace(badChars, escapedChar); } return string; } }, setting: function(name, value) { module.debug('Changing setting', name, value); if( $.isPlainObject(name) ) { $.extend(true, settings, name); } else if(value !== undefined) { if($.isPlainObject(settings[name])) { $.extend(true, settings[name], value); } else { settings[name] = value; } } else { return settings[name]; } }, internal: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function() { if(!settings.silent && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function() { if(!settings.silent && settings.verbose && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function() { if(!settings.silent) { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); } }, performance: { log: function(message) { var currentTime, executionTime, previousTime ; if(settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Name' : message[0], 'Arguments' : [].slice.call(message, 1) || '', 'Element' : element, 'Execution Time' : executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function(index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if(moduleSelector) { title += ' \'' + moduleSelector + '\''; } if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if(console.table) { console.table(performance); } else { $.each(performance, function(index, data) { console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function(query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if(typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function(depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { object = object[camelCaseValue]; } else if( object[camelCaseValue] !== undefined ) { found = object[camelCaseValue]; return false; } else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { object = object[value]; } else if( object[value] !== undefined ) { found = object[value]; return false; } else { module.error(error.method, query); return false; } }); } if ( $.isFunction( found ) ) { response = found.apply(context, passedArguments); } else if(found !== undefined) { response = found; } if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if(response !== undefined) { returnedValue = response; } return found; } }; if(methodInvoked) { if(instance === undefined) { module.initialize(); } module.invoke(query); } else { if(instance !== undefined) { instance.invoke('destroy'); } module.initialize(); } }) ; return (returnedValue !== undefined) ? returnedValue : $allModules ; }; $.fn.dropdown.settings = { silent : false, debug : false, verbose : false, performance : true, on : 'click', // what event should show menu action on item selection action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) values : false, // specify values to use for dropdown clearable : false, // whether the value of the dropdown can be cleared apiSettings : false, selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used minCharacters : 0, // Minimum characters required to trigger API call filterRemoteData : false, // Whether API results should be filtered after being returned for query term saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh throttle : 200, // How long to wait after last user input to search remotely context : window, // Context to use when determining if on screen direction : 'auto', // Whether dropdown should always open in one direction keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing match : 'both', // what to match against with search selection (both, text, or label) fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) placeholder : 'auto', // whether to convert blank the values will be delimited with this character showOnFocus : true, // show menu on focus allowReselection : false, // whether current value should trigger callbacks when reselected allowTab : true, // add tabindex to element allowCategorySelection : false, // allow elements with sub-menus to be selected fireOnInit : false, // Whether callbacks should fire when initializing dropdown values transition : 'auto', // auto transition will slide down or up based on direction duration : 200, // duration of transition displayType : false, // displayType of transition glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width headerDivider : true, // whether option headers should have an additional divider line underneath when converted from requires multiple property to be set to correctly preserve multiple values', method : 'The method you called is not defined.', noAPI : 'The API module is required to load resources remotely', noStorage : 'Saving remote data requires session storage', noTransition : 'This module requires ui transitions ', noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including as a polyfill.' }, regExp : { escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, quote : /"/g }, metadata : { defaultText : 'defaultText', defaultValue : 'defaultValue', placeholderText : 'placeholder', text : 'text', value : 'value' }, // property names for remote query fields: { remoteValues : 'results', // grouping for api results values : 'values', // grouping for all dropdown values disabled : 'disabled', // whether value should be disabled name : 'name', // displayed dropdown text description : 'description', // displayed dropdown description descriptionVertical : 'descriptionVertical', // whether description should be vertical value : 'value', // actual dropdown value text : 'text', // displayed text when selected type : 'type', // type of dropdown element image : 'image', // optional image path imageClass : 'imageClass', // optional individual class for image icon : 'icon', // optional icon name iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) class : 'class', // optional individual class for item/header divider : 'divider' // optional divider append for group headers }, keys : { backspace : 8, delimiter : 188, // comma deleteKey : 46, enter : 13, escape : 27, pageUp : 33, pageDown : 34, leftArrow : 37, upArrow : 38, rightArrow : 39, downArrow : 40 }, selector : { addition : '.addition', divider : '.divider, .header', dropdown : '.ui.dropdown', hidden : '.hidden', icon : '> .dropdown.icon', input : '> input[type="hidden"], > select', item : '.item', label : '> .label', remove : '> .label > .delete.icon', siblingLabel : '.label', menu : '.menu', message : '.message', menuIcon : '.dropdown.icon', search : 'input.search, .menu > .search > input, .menu input.search', sizer : '> span.sizer', text : '> .text:not(.icon)', unselectable : '.disabled, .filtered', clearIcon : '> .remove.icon' }, className : { active : 'active', addition : 'addition', animating : 'animating', description : 'description', descriptionVertical : 'vertical', disabled : 'disabled', empty : 'empty', dropdown : 'ui dropdown', filtered : 'filtered', hidden : 'hidden transition', icon : 'icon', image : 'image', item : 'item', label : 'ui label', loading : 'loading', menu : 'menu', message : 'message', multiple : 'multiple', placeholder : 'default', sizer : 'sizer', search : 'search', selected : 'selected', selection : 'selection', text : 'text', upward : 'upward', leftward : 'left', visible : 'visible', clearable : 'clearable', noselection : 'noselection', delete : 'delete', header : 'header', divider : 'divider', groupIcon : '', unfilterable : 'unfilterable' } }; /* Templates */ $.fn.dropdown.settings.templates = { deQuote: function(string, encode) { return String(string).replace(/"/g,encode ? """ : ""); }, escape: function(string, preserveHTML) { if (preserveHTML){ return string; } var badChars = /[<>"'`]/g, shouldEscape = /[&<>"'`]/, escape = { "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }, escapedChar = function(chr) { return escape[chr]; } ; if(shouldEscape.test(string)) { string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); return string.replace(badChars, escapedChar); } return string; }, // generates dropdown from select values dropdown: function(select, fields, preserveHTML, className) { var placeholder = select.placeholder || false, html = '', escape = $.fn.dropdown.settings.templates.escape ; html += ''; if(placeholder) { html += '
' + escape(placeholder,preserveHTML) + '
'; } else { html += '
'; } html += '
'; html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); html += '
'; return html; }, // generates just menu from select menu: function(response, fields, preserveHTML, className) { var values = response[fields.values] || [], html = '', escape = $.fn.dropdown.settings.templates.escape, deQuote = $.fn.dropdown.settings.templates.deQuote ; $.each(values, function(index, option) { var itemType = (option[fields.type]) ? option[fields.type] : 'item', isMenu = itemType.indexOf('menu') !== -1 ; if( itemType === 'item' || isMenu) { var maybeText = (option[fields.text]) ? ' data-text="' + deQuote(option[fields.text],true) + '"' : '', maybeDisabled = (option[fields.disabled]) ? className.disabled+' ' : '', maybeDescriptionVertical = (option[fields.descriptionVertical]) ? className.descriptionVertical+' ' : '', hasDescription = (escape(option[fields.description] || '', preserveHTML) != '') ; html += '
'; if (isMenu) { html += ''; } if(option[fields.image]) { html += ''; } if(option[fields.icon]) { html += ''; } if(hasDescription){ html += ''+ escape(option[fields.description] || '', preserveHTML) + ''; html += (!isMenu) ? '' : ''; } if (isMenu) { html += ''; } html += escape(option[fields.name] || '', preserveHTML); if (isMenu) { html += ''; html += '
'; html += $.fn.dropdown.settings.templates.menu(option, fields, preserveHTML, className); html += '
'; } else if(hasDescription){ html += '
'; } html += '
'; } else if (itemType === 'header') { var groupName = escape(option[fields.name] || '', preserveHTML), groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon ; if(groupName !== '' || groupIcon !== '') { html += '
'; if (groupIcon !== '') { html += ''; } html += groupName; html += '
'; } if(option[fields.divider]){ html += '
'; } } }); return html; }, // generates label for multiselect label: function(value, text, preserveHTML, className) { var escape = $.fn.dropdown.settings.templates.escape; return escape(text,preserveHTML) + ''; }, // generates messages like "No results" message: function(message) { return message; }, // generates user addition to selection menu addition: function(choice) { return choice; } }; })( jQuery, window, document ); /*! * # Fomantic-UI 2.8.8 - Form Validation * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) ? self : Function('return this')() ; $.fn.form = function(parameters) { var $allModules = $(this), moduleSelector = $allModules.selector || '', time = new Date().getTime(), performance = [], query = arguments[0], legacyParameters = arguments[1], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), returnedValue ; $allModules .each(function() { var $module = $(this), element = this, formErrors = [], keyHeldDown = false, // set at run-time $field, $group, $message, $prompt, $submit, $clear, $reset, settings, validation, metadata, selector, className, regExp, error, namespace, moduleNamespace, eventNamespace, submitting = false, dirty = false, history = ['clean', 'clean'], instance, module ; module = { initialize: function() { // settings grabbed at run time module.get.settings(); if(methodInvoked) { if(instance === undefined) { module.instantiate(); } module.invoke(query); } else { if(instance !== undefined) { instance.invoke('destroy'); module.refresh(); } module.verbose('Initializing form validation', $module, settings); module.bindEvents(); module.set.defaults(); if (settings.autoCheckRequired) { module.set.autoCheck(); } module.instantiate(); } }, instantiate: function() { module.verbose('Storing instance of module', module); instance = module; $module .data(moduleNamespace, module) ; }, destroy: function() { module.verbose('Destroying previous module', instance); module.removeEvents(); $module .removeData(moduleNamespace) ; }, refresh: function() { module.verbose('Refreshing selector cache'); $field = $module.find(selector.field); $group = $module.find(selector.group); $message = $module.find(selector.message); $prompt = $module.find(selector.prompt); $submit = $module.find(selector.submit); $clear = $module.find(selector.clear); $reset = $module.find(selector.reset); }, submit: function() { module.verbose('Submitting form', $module); submitting = true; $module.submit(); }, attachEvents: function(selector, action) { action = action || 'submit'; $(selector).on('click' + eventNamespace, function(event) { module[action](); event.preventDefault(); }); }, bindEvents: function() { module.verbose('Attaching form events'); $module .on('submit' + eventNamespace, module.validate.form) .on('blur' + eventNamespace, selector.field, module.event.field.blur) .on('click' + eventNamespace, selector.submit, module.submit) .on('click' + eventNamespace, selector.reset, module.reset) .on('click' + eventNamespace, selector.clear, module.clear) ; if(settings.keyboardShortcuts) { $module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown); } $field.each(function(index, el) { var $input = $(el), type = $input.prop('type'), inputEvent = module.get.changeEvent(type, $input) ; $input.on(inputEvent + eventNamespace, module.event.field.change); }); // Dirty events if (settings.preventLeaving) { $(window).on('beforeunload' + eventNamespace, module.event.beforeUnload); } $field.on('change click keyup keydown blur', function(e) { module.determine.isDirty(); }); $module.on('dirty' + eventNamespace, function(e) { settings.onDirty.call(); }); $module.on('clean' + eventNamespace, function(e) { settings.onClean.call(); }) }, clear: function() { $field.each(function (index, el) { var $field = $(el), $element = $field.parent(), $fieldGroup = $field.closest($group), $prompt = $fieldGroup.find(selector.prompt), $calendar = $field.closest(selector.uiCalendar), defaultValue = $field.data(metadata.defaultValue) || '', isCheckbox = $element.is(selector.uiCheckbox), isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), isErrored = $fieldGroup.hasClass(className.error) ; if(isErrored) { module.verbose('Resetting error on field', $fieldGroup); $fieldGroup.removeClass(className.error); $prompt.remove(); } if(isDropdown) { module.verbose('Resetting dropdown value', $element, defaultValue); $element.dropdown('clear', true); } else if(isCheckbox) { $field.prop('checked', false); } else if (isCalendar) { $calendar.calendar('clear'); } else { module.verbose('Resetting field value', $field, defaultValue); $field.val(''); } }); module.remove.states(); }, reset: function() { $field.each(function (index, el) { var $field = $(el), $element = $field.parent(), $fieldGroup = $field.closest($group), $calendar = $field.closest(selector.uiCalendar), $prompt = $fieldGroup.find(selector.prompt), defaultValue = $field.data(metadata.defaultValue), isCheckbox = $element.is(selector.uiCheckbox), isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), isErrored = $fieldGroup.hasClass(className.error) ; if(defaultValue === undefined) { return; } if(isErrored) { module.verbose('Resetting error on field', $fieldGroup); $fieldGroup.removeClass(className.error); $prompt.remove(); } if(isDropdown) { module.verbose('Resetting dropdown value', $element, defaultValue); $element.dropdown('restore defaults', true); } else if(isCheckbox) { module.verbose('Resetting checkbox value', $element, defaultValue); $field.prop('checked', defaultValue); } else if (isCalendar) { $calendar.calendar('set date', defaultValue); } else { module.verbose('Resetting field value', $field, defaultValue); $field.val(defaultValue); } }); module.remove.states(); }, determine: { isValid: function() { var allValid = true ; $.each(validation, function(fieldName, field) { if( !( module.validate.field(field, fieldName, true) ) ) { allValid = false; } }); return allValid; }, isDirty: function(e) { var formIsDirty = false; $field.each(function(index, el) { var $el = $(el), isCheckbox = ($el.filter(selector.checkbox).length > 0), isDirty ; if (isCheckbox) { isDirty = module.is.checkboxDirty($el); } else { isDirty = module.is.fieldDirty($el); } $el.data(settings.metadata.isDirty, isDirty); formIsDirty |= isDirty; }); if (formIsDirty) { module.set.dirty(); } else { module.set.clean(); } } }, is: { bracketedRule: function(rule) { return (rule.type && rule.type.match(settings.regExp.bracket)); }, // duck type rule test shorthandRules: function(rules) { return (typeof rules == 'string' || Array.isArray(rules)); }, empty: function($field) { if(!$field || $field.length === 0) { return true; } else if($field.is(selector.checkbox)) { return !$field.is(':checked'); } else { return module.is.blank($field); } }, blank: function($field) { return String($field.val()).trim() === ''; }, valid: function(field, showErrors) { var allValid = true ; if(field) { module.verbose('Checking if field is valid', field); return module.validate.field(validation[field], field, !!showErrors); } else { module.verbose('Checking if form is valid'); $.each(validation, function(fieldName, field) { if( !module.is.valid(fieldName, showErrors) ) { allValid = false; } }); return allValid; } }, dirty: function() { return dirty; }, clean: function() { return !dirty; }, fieldDirty: function($el) { var initialValue = $el.data(metadata.defaultValue); // Explicitly check for null/undefined here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work if (initialValue == null) { initialValue = ''; } else if(Array.isArray(initialValue)) { initialValue = initialValue.toString(); } var currentValue = $el.val(); if (currentValue == null) { currentValue = ''; } // multiple select values are returned as arrays which are never equal, so do string conversion first else if(Array.isArray(currentValue)) { currentValue = currentValue.toString(); } // Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison var boolRegex = /^(true|false)$/i; var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue); if (isBoolValue) { var regex = new RegExp("^" + initialValue + "$", "i"); return !regex.test(currentValue); } return currentValue !== initialValue; }, checkboxDirty: function($el) { var initialValue = $el.data(metadata.defaultValue); var currentValue = $el.is(":checked"); return initialValue !== currentValue; }, justDirty: function() { return (history[0] === 'dirty'); }, justClean: function() { return (history[0] === 'clean'); } }, removeEvents: function() { $module.off(eventNamespace); $field.off(eventNamespace); $submit.off(eventNamespace); $field.off(eventNamespace); }, event: { field: { keydown: function(event) { var $field = $(this), key = event.which, isInput = $field.is(selector.input), isCheckbox = $field.is(selector.checkbox), isInDropdown = ($field.closest(selector.uiDropdown).length > 0), keyCode = { enter : 13, escape : 27 } ; if( key == keyCode.escape) { module.verbose('Escape key pressed blurring field'); $field[0] .blur() ; } if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) { if(!keyHeldDown) { $field.one('keyup' + eventNamespace, module.event.field.keyup); module.submit(); module.debug('Enter pressed on input submitting form'); } keyHeldDown = true; } }, keyup: function() { keyHeldDown = false; }, blur: function(event) { var $field = $(this), $fieldGroup = $field.closest($group), validationRules = module.get.validation($field) ; if(validationRules && (settings.on == 'blur' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) { module.debug('Revalidating field', $field, validationRules); module.validate.field( validationRules ); if(!settings.inline) { module.validate.form(false,true); } } }, change: function(event) { var $field = $(this), $fieldGroup = $field.closest($group), validationRules = module.get.validation($field) ; if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) { clearTimeout(module.timer); module.timer = setTimeout(function() { module.debug('Revalidating field', $field, validationRules); module.validate.field( validationRules ); if(!settings.inline) { module.validate.form(false,true); } }, settings.delay); } } }, beforeUnload: function(event) { if (module.is.dirty() && !submitting) { var event = event || window.event; // For modern browsers if (event) { event.returnValue = settings.text.leavingMessage; } // For olders... return settings.text.leavingMessage; } } }, get: { ancillaryValue: function(rule) { if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) { return false; } return (rule.value !== undefined) ? rule.value : rule.type.match(settings.regExp.bracket)[1] + '' ; }, ruleName: function(rule) { if( module.is.bracketedRule(rule) ) { return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], ''); } return rule.type; }, changeEvent: function(type, $input) { if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) { return 'change'; } else { return module.get.inputEvent(); } }, inputEvent: function() { return (document.createElement('input').oninput !== undefined) ? 'input' : (document.createElement('input').onpropertychange !== undefined) ? 'propertychange' : 'keyup' ; }, fieldsFromShorthand: function(fields) { var fullFields = {} ; $.each(fields, function(name, rules) { if (!Array.isArray(rules) && typeof rules === 'object') { fullFields[name] = rules; } else { if (typeof rules == 'string') { rules = [rules]; } fullFields[name] = { rules: [] }; $.each(rules, function (index, rule) { fullFields[name].rules.push({type: rule}); }); } }); return fullFields; }, prompt: function(rule, field) { var ruleName = module.get.ruleName(rule), ancillary = module.get.ancillaryValue(rule), $field = module.get.field(field.identifier), value = $field.val(), prompt = $.isFunction(rule.prompt) ? rule.prompt(value) : rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule, requiresValue = (prompt.search('{value}') !== -1), requiresName = (prompt.search('{name}') !== -1), $label, name, parts, suffixPrompt ; if(ancillary && ancillary.indexOf('..') >= 0) { parts = ancillary.split('..', 2); if(!rule.prompt) { suffixPrompt = ( parts[0] === '' ? settings.prompt.maxValue.replace(/\{ruleValue\}/g,'{max}') : parts[1] === '' ? settings.prompt.minValue.replace(/\{ruleValue\}/g,'{min}') : settings.prompt.range ); prompt += suffixPrompt.replace(/\{name\}/g, ' ' + settings.text.and); } prompt = prompt.replace(/\{min\}/g, parts[0]); prompt = prompt.replace(/\{max\}/g, parts[1]); } if(requiresValue) { prompt = prompt.replace(/\{value\}/g, $field.val()); } if(requiresName) { $label = $field.closest(selector.group).find('label').eq(0); name = ($label.length == 1) ? $label.text() : $field.prop('placeholder') || settings.text.unspecifiedField ; prompt = prompt.replace(/\{name\}/g, name); } prompt = prompt.replace(/\{identifier\}/g, field.identifier); prompt = prompt.replace(/\{ruleValue\}/g, ancillary); if(!rule.prompt) { module.verbose('Using default validation prompt for type', prompt, ruleName); } return prompt; }, settings: function() { if($.isPlainObject(parameters)) { var keys = Object.keys(parameters), isLegacySettings = (keys.length > 0) ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined) : false ; if(isLegacySettings) { // 1.x (ducktyped) settings = $.extend(true, {}, $.fn.form.settings, legacyParameters); validation = $.extend({}, $.fn.form.settings.defaults, parameters); module.error(settings.error.oldSyntax, element); module.verbose('Extending settings from legacy parameters', validation, settings); } else { // 2.x if(parameters.fields) { parameters.fields = module.get.fieldsFromShorthand(parameters.fields); } settings = $.extend(true, {}, $.fn.form.settings, parameters); validation = $.extend({}, $.fn.form.settings.defaults, settings.fields); module.verbose('Extending settings', validation, settings); } } else { settings = $.fn.form.settings; validation = $.fn.form.settings.defaults; module.verbose('Using default form validation', validation, settings); } // shorthand namespace = settings.namespace; metadata = settings.metadata; selector = settings.selector; className = settings.className; regExp = settings.regExp; error = settings.error; moduleNamespace = 'module-' + namespace; eventNamespace = '.' + namespace; // grab instance instance = $module.data(moduleNamespace); // refresh selector cache (instance || module).refresh(); }, field: function(identifier) { module.verbose('Finding field with identifier', identifier); identifier = module.escape.string(identifier); var t; if((t=$field.filter('#' + identifier)).length > 0 ) { return t; } if((t=$field.filter('[name="' + identifier +'"]')).length > 0 ) { return t; } if((t=$field.filter('[name="' + identifier +'[]"]')).length > 0 ) { return t; } if((t=$field.filter('[data-' + metadata.validate + '="'+ identifier +'"]')).length > 0 ) { return t; } return $(''); }, fields: function(fields) { var $fields = $() ; $.each(fields, function(index, name) { $fields = $fields.add( module.get.field(name) ); }); return $fields; }, validation: function($field) { var fieldValidation, identifier ; if(!validation) { return false; } $.each(validation, function(fieldName, field) { identifier = field.identifier || fieldName; $.each(module.get.field(identifier), function(index, groupField) { if(groupField == $field[0]) { field.identifier = identifier; fieldValidation = field; return false; } }); }); return fieldValidation || false; }, value: function (field) { var fields = [], results ; fields.push(field); results = module.get.values.call(element, fields); return results[field]; }, values: function (fields) { var $fields = Array.isArray(fields) ? module.get.fields(fields) : $field, values = {} ; $fields.each(function(index, field) { var $field = $(field), $calendar = $field.closest(selector.uiCalendar), name = $field.prop('name'), value = $field.val(), isCheckbox = $field.is(selector.checkbox), isRadio = $field.is(selector.radio), isMultiple = (name.indexOf('[]') !== -1), isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), isChecked = (isCheckbox) ? $field.is(':checked') : false ; if(name) { if(isMultiple) { name = name.replace('[]', ''); if(!values[name]) { values[name] = []; } if(isCheckbox) { if(isChecked) { values[name].push(value || true); } else { values[name].push(false); } } else { values[name].push(value); } } else { if(isRadio) { if(values[name] === undefined || values[name] === false) { values[name] = (isChecked) ? value || true : false ; } } else if(isCheckbox) { if(isChecked) { values[name] = value || true; } else { values[name] = false; } } else if(isCalendar) { var date = $calendar.calendar('get date'); if (date !== null) { if (settings.dateHandling == 'date') { values[name] = date; } else if(settings.dateHandling == 'input') { values[name] = $calendar.calendar('get input date') } else if (settings.dateHandling == 'formatter') { var type = $calendar.calendar('setting', 'type'); switch(type) { case 'date': values[name] = settings.formatter.date(date); break; case 'datetime': values[name] = settings.formatter.datetime(date); break; case 'time': values[name] = settings.formatter.time(date); break; case 'month': values[name] = settings.formatter.month(date); break; case 'year': values[name] = settings.formatter.year(date); break; default: module.debug('Wrong calendar mode', $calendar, type); values[name] = ''; } } } else { values[name] = ''; } } else { values[name] = value; } } } }); return values; }, dirtyFields: function() { return $field.filter(function(index, e) { return $(e).data(metadata.isDirty); }); } }, has: { field: function(identifier) { module.verbose('Checking for existence of a field with identifier', identifier); identifier = module.escape.string(identifier); if(typeof identifier !== 'string') { module.error(error.identifier, identifier); } if($field.filter('#' + identifier).length > 0 ) { return true; } else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { return true; } else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { return true; } return false; } }, can: { useElement: function(element){ if ($.fn[element] !== undefined) { return true; } module.error(error.noElement.replace('{element}',element)); return false; } }, escape: { string: function(text) { text = String(text); return text.replace(regExp.escape, '\\$&'); } }, add: { // alias rule: function(name, rules) { module.add.field(name, rules); }, field: function(name, rules) { // Validation should have at least a standard format if(validation[name] === undefined || validation[name].rules === undefined) { validation[name] = { rules: [] }; } var newValidation = { rules: [] } ; if(module.is.shorthandRules(rules)) { rules = Array.isArray(rules) ? rules : [rules] ; $.each(rules, function(_index, rule) { newValidation.rules.push({ type: rule }); }); } else { newValidation.rules = rules.rules; } // For each new rule, check if there's not already one with the same type $.each(newValidation.rules, function (_index, rule) { if ($.grep(validation[name].rules, function(item){ return item.type == rule.type; }).length == 0) { validation[name].rules.push(rule); } }); module.debug('Adding rules', newValidation.rules, validation); }, fields: function(fields) { validation = $.extend({}, validation, module.get.fieldsFromShorthand(fields)); }, prompt: function(identifier, errors, internal) { var $field = module.get.field(identifier), $fieldGroup = $field.closest($group), $prompt = $fieldGroup.children(selector.prompt), promptExists = ($prompt.length !== 0) ; errors = (typeof errors == 'string') ? [errors] : errors ; module.verbose('Adding field error state', identifier); if(!internal) { $fieldGroup .addClass(className.error) ; } if(settings.inline) { if(!promptExists) { $prompt = settings.templates.prompt(errors, className.label); $prompt .appendTo($fieldGroup) ; } $prompt .html(errors[0]) ; if(!promptExists) { if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { module.verbose('Displaying error with css transition', settings.transition); $prompt.transition(settings.transition + ' in', settings.duration); } else { module.verbose('Displaying error with fallback javascript animation'); $prompt .fadeIn(settings.duration) ; } } else { module.verbose('Inline errors are disabled, no inline error added', identifier); } } }, errors: function(errors) { module.debug('Adding form error messages', errors); module.set.error(); $message .html( settings.templates.error(errors) ) ; } }, remove: { errors: function() { module.debug('Removing form error messages'); $message.empty(); }, states: function() { $module.removeClass(className.error).removeClass(className.success); if(!settings.inline) { module.remove.errors(); } module.determine.isDirty(); }, rule: function(field, rule) { var rules = Array.isArray(rule) ? rule : [rule] ; if(validation[field] === undefined || !Array.isArray(validation[field].rules)) { return; } if(rule === undefined) { module.debug('Removed all rules'); validation[field].rules = []; return; } $.each(validation[field].rules, function(index, rule) { if(rule && rules.indexOf(rule.type) !== -1) { module.debug('Removed rule', rule.type); validation[field].rules.splice(index, 1); } }); }, field: function(field) { var fields = Array.isArray(field) ? field : [field] ; $.each(fields, function(index, field) { module.remove.rule(field); }); }, // alias rules: function(field, rules) { if(Array.isArray(field)) { $.each(field, function(index, field) { module.remove.rule(field, rules); }); } else { module.remove.rule(field, rules); } }, fields: function(fields) { module.remove.field(fields); }, prompt: function(identifier) { var $field = module.get.field(identifier), $fieldGroup = $field.closest($group), $prompt = $fieldGroup.children(selector.prompt) ; $fieldGroup .removeClass(className.error) ; if(settings.inline && $prompt.is(':visible')) { module.verbose('Removing prompt for field', identifier); if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { $prompt.transition(settings.transition + ' out', settings.duration, function() { $prompt.remove(); }); } else { $prompt .fadeOut(settings.duration, function(){ $prompt.remove(); }) ; } } } }, set: { success: function() { $module .removeClass(className.error) .addClass(className.success) ; }, defaults: function () { $field.each(function (index, el) { var $el = $(el), $parent = $el.parent(), isCheckbox = ($el.filter(selector.checkbox).length > 0), isDropdown = $parent.is(selector.uiDropdown) && module.can.useElement('dropdown'), $calendar = $el.closest(selector.uiCalendar), isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), value = (isCheckbox) ? $el.is(':checked') : $el.val() ; if (isDropdown) { $parent.dropdown('save defaults'); } else if (isCalendar) { $calendar.calendar('refresh'); } $el.data(metadata.defaultValue, value); $el.data(metadata.isDirty, false); }); }, error: function() { $module .removeClass(className.success) .addClass(className.error) ; }, value: function (field, value) { var fields = {} ; fields[field] = value; return module.set.values.call(element, fields); }, values: function (fields) { if($.isEmptyObject(fields)) { return; } $.each(fields, function(key, value) { var $field = module.get.field(key), $element = $field.parent(), $calendar = $field.closest(selector.uiCalendar), isMultiple = Array.isArray(value), isCheckbox = $element.is(selector.uiCheckbox) && module.can.useElement('checkbox'), isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), isRadio = ($field.is(selector.radio) && isCheckbox), isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), fieldExists = ($field.length > 0), $multipleField ; if(fieldExists) { if(isMultiple && isCheckbox) { module.verbose('Selecting multiple', value, $field); $element.checkbox('uncheck'); $.each(value, function(index, value) { $multipleField = $field.filter('[value="' + value + '"]'); $element = $multipleField.parent(); if($multipleField.length > 0) { $element.checkbox('check'); } }); } else if(isRadio) { module.verbose('Selecting radio value', value, $field); $field.filter('[value="' + value + '"]') .parent(selector.uiCheckbox) .checkbox('check') ; } else if(isCheckbox) { module.verbose('Setting checkbox value', value, $element); if(value === true || value === 1) { $element.checkbox('check'); } else { $element.checkbox('uncheck'); } } else if(isDropdown) { module.verbose('Setting dropdown value', value, $element); $element.dropdown('set selected', value); } else if (isCalendar) { $calendar.calendar('set date',value); } else { module.verbose('Setting field value', value, $field); $field.val(value); } } }); }, dirty: function() { module.verbose('Setting state dirty'); dirty = true; history[0] = history[1]; history[1] = 'dirty'; if (module.is.justClean()) { $module.trigger('dirty'); } }, clean: function() { module.verbose('Setting state clean'); dirty = false; history[0] = history[1]; history[1] = 'clean'; if (module.is.justDirty()) { $module.trigger('clean'); } }, asClean: function() { module.set.defaults(); module.set.clean(); }, asDirty: function() { module.set.defaults(); module.set.dirty(); }, autoCheck: function() { module.debug('Enabling auto check on required fields'); $field.each(function (_index, el) { var $el = $(el), $elGroup = $(el).closest($group), isCheckbox = ($el.filter(selector.checkbox).length > 0), isRequired = $el.prop('required') || $elGroup.hasClass(className.required) || $elGroup.parent().hasClass(className.required), isDisabled = $el.is(':disabled') || $elGroup.hasClass(className.disabled) || $elGroup.parent().hasClass(className.disabled), validation = module.get.validation($el), hasEmptyRule = validation ? $.grep(validation.rules, function(rule) { return rule.type == "empty" }) !== 0 : false, identifier = validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate) ; if (isRequired && !isDisabled && !hasEmptyRule && identifier !== undefined) { if (isCheckbox) { module.verbose("Adding 'checked' rule on field", identifier); module.add.rule(identifier, "checked"); } else { module.verbose("Adding 'empty' rule on field", identifier); module.add.rule(identifier, "empty"); } } }); }, optional: function(identifier, bool) { bool = (bool !== false); $.each(validation, function(fieldName, field) { if (identifier == fieldName || identifier == field.identifier) { field.optional = bool; } }); } }, validate: { form: function(event, ignoreCallbacks) { var values = module.get.values(); // input keydown event will fire submit repeatedly by browser default if(keyHeldDown) { return false; } // reset errors formErrors = []; if( module.determine.isValid() ) { module.debug('Form has no validation errors, submitting'); module.set.success(); if(!settings.inline) { module.remove.errors(); } if(ignoreCallbacks !== true) { return settings.onSuccess.call(element, event, values); } } else { module.debug('Form has errors'); submitting = false; module.set.error(); if(!settings.inline) { module.add.errors(formErrors); } // prevent ajax submit if(event && $module.data('moduleApi') !== undefined) { event.stopImmediatePropagation(); } if(settings.errorFocus) { var focusElement, hasTabIndex = true; if (typeof settings.errorFocus === 'string') { focusElement = $(settings.errorFocus); hasTabIndex = focusElement.is('[tabindex]'); // to be able to focus/scroll into non input elements we need a tabindex if (!hasTabIndex) { focusElement.attr('tabindex',-1); } } else { focusElement = $group.filter('.' + className.error).first().find(selector.field); } focusElement.focus(); // only remove tabindex if it was dynamically created above if (!hasTabIndex){ focusElement.removeAttr('tabindex'); } } if(ignoreCallbacks !== true) { return settings.onFailure.call(element, formErrors, values); } } }, // takes a validation object and returns whether field passes validation field: function(field, fieldName, showErrors) { showErrors = (showErrors !== undefined) ? showErrors : true ; if(typeof field == 'string') { module.verbose('Validating field', field); fieldName = field; field = validation[field]; } var identifier = field.identifier || fieldName, $field = module.get.field(identifier), $dependsField = (field.depends) ? module.get.field(field.depends) : false, fieldValid = true, fieldErrors = [] ; if(!field.identifier) { module.debug('Using field name as identifier', identifier); field.identifier = identifier; } var isDisabled = !$field.filter(':not(:disabled)').length; if(isDisabled) { module.debug('Field is disabled. Skipping', identifier); } else if(field.optional && module.is.blank($field)){ module.debug('Field is optional and blank. Skipping', identifier); } else if(field.depends && module.is.empty($dependsField)) { module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField); } else if(field.rules !== undefined) { if(showErrors) { $field.closest($group).removeClass(className.error); } $.each(field.rules, function(index, rule) { if( module.has.field(identifier)) { var invalidFields = module.validate.rule(field, rule,true) || []; if (invalidFields.length>0){ module.debug('Field is invalid', identifier, rule.type); fieldErrors.push(module.get.prompt(rule, field)); fieldValid = false; if(showErrors){ $(invalidFields).closest($group).addClass(className.error); } } } }); } if(fieldValid) { if(showErrors) { module.remove.prompt(identifier, fieldErrors); settings.onValid.call($field); } } else { if(showErrors) { formErrors = formErrors.concat(fieldErrors); module.add.prompt(identifier, fieldErrors, true); settings.onInvalid.call($field, fieldErrors); } return false; } return true; }, // takes validation rule and returns whether field passes rule rule: function(field, rule, internal) { var $field = module.get.field(field.identifier), ancillary = module.get.ancillaryValue(rule), ruleName = module.get.ruleName(rule), ruleFunction = settings.rules[ruleName], invalidFields = [], isCheckbox = $field.is(selector.checkbox), isValid = function(field){ var value = (isCheckbox ? $(field).filter(':checked').val() : $(field).val()); // cast to string avoiding encoding special values value = (value === undefined || value === '' || value === null) ? '' : (settings.shouldTrim && rule.shouldTrim !== false) || rule.shouldTrim ? String(value + '').trim() : String(value + '') ; return ruleFunction.call(field, value, ancillary, $module); } ; if( !$.isFunction(ruleFunction) ) { module.error(error.noRule, ruleName); return; } if(isCheckbox) { if (!isValid($field)) { invalidFields = $field; } } else { $.each($field, function (index, field) { if (!isValid(field)) { invalidFields.push(field); } }); } return internal ? invalidFields : !(invalidFields.length>0); } }, setting: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, settings, name); } else if(value !== undefined) { settings[name] = value; } else { return settings[name]; } }, internal: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function() { if(!settings.silent && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function() { if(!settings.silent && settings.verbose && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function() { if(!settings.silent) { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); } }, performance: { log: function(message) { var currentTime, executionTime, previousTime ; if(settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Name' : message[0], 'Arguments' : [].slice.call(message, 1) || '', 'Element' : element, 'Execution Time' : executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function(index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if(moduleSelector) { title += ' \'' + moduleSelector + '\''; } if($allModules.length > 1) { title += ' ' + '(' + $allModules.length + ')'; } if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if(console.table) { console.table(performance); } else { $.each(performance, function(index, data) { console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function(query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if(typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function(depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { object = object[camelCaseValue]; } else if( object[camelCaseValue] !== undefined ) { found = object[camelCaseValue]; return false; } else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { object = object[value]; } else if( object[value] !== undefined ) { found = object[value]; return false; } else { return false; } }); } if( $.isFunction( found ) ) { response = found.apply(context, passedArguments); } else if(found !== undefined) { response = found; } if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if(response !== undefined) { returnedValue = response; } return found; } }; module.initialize(); }) ; return (returnedValue !== undefined) ? returnedValue : this ; }; $.fn.form.settings = { name : 'Form', namespace : 'form', debug : false, verbose : false, performance : true, fields : false, keyboardShortcuts : true, on : 'submit', inline : false, delay : 200, revalidate : true, shouldTrim : true, transition : 'scale', duration : 200, autoCheckRequired : false, preventLeaving : false, errorFocus : false, dateHandling : 'date', // 'date', 'input', 'formatter' onValid : function() {}, onInvalid : function() {}, onSuccess : function() { return true; }, onFailure : function() { return false; }, onDirty : function() {}, onClean : function() {}, metadata : { defaultValue : 'default', validate : 'validate', isDirty : 'isDirty' }, regExp: { htmlID : /^[a-zA-Z][\w:.-]*$/g, bracket : /\[(.*)\]/i, decimal : /^\d+\.?\d*$/, email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|:,=@]/g, flags : /^\/(.*)\/(.*)?/, integer : /^\-?\d+$/, number : /^\-?\d*(\.\d+)?$/, url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i }, text: { and : 'and', unspecifiedRule : 'Please enter a valid value', unspecifiedField : 'This field', leavingMessage : 'There are unsaved changes on this page which will be discarded if you continue.' }, prompt: { range : '{name} must be in a range from {min} to {max}', maxValue : '{name} must have a maximum value of {ruleValue}', minValue : '{name} must have a minimum value of {ruleValue}', empty : '{name} must have a value', checked : '{name} must be checked', email : '{name} must be a valid e-mail', url : '{name} must be a valid url', regExp : '{name} is not formatted correctly', integer : '{name} must be an integer', decimal : '{name} must be a decimal number', number : '{name} must be set to a number', is : '{name} must be "{ruleValue}"', isExactly : '{name} must be exactly "{ruleValue}"', not : '{name} cannot be set to "{ruleValue}"', notExactly : '{name} cannot be set to exactly "{ruleValue}"', contain : '{name} must contain "{ruleValue}"', containExactly : '{name} must contain exactly "{ruleValue}"', doesntContain : '{name} cannot contain "{ruleValue}"', doesntContainExactly : '{name} cannot contain exactly "{ruleValue}"', minLength : '{name} must be at least {ruleValue} characters', length : '{name} must be at least {ruleValue} characters', exactLength : '{name} must be exactly {ruleValue} characters', maxLength : '{name} cannot be longer than {ruleValue} characters', match : '{name} must match {ruleValue} field', different : '{name} must have a different value than {ruleValue} field', creditCard : '{name} must be a valid credit card number', minCount : '{name} must have at least {ruleValue} choices', exactCount : '{name} must have exactly {ruleValue} choices', maxCount : '{name} must have {ruleValue} or less choices' }, selector : { checkbox : 'input[type="checkbox"], input[type="radio"]', clear : '.clear', field : 'input:not(.search):not([type="file"]), textarea, select', group : '.field', input : 'input:not([type="file"])', message : '.error.message', prompt : '.prompt.label', radio : 'input[type="radio"]', reset : '.reset:not([type="reset"])', submit : '.submit:not([type="submit"])', uiCheckbox : '.ui.checkbox', uiDropdown : '.ui.dropdown', uiCalendar : '.ui.calendar' }, className : { error : 'error', label : 'ui basic red pointing prompt label', pressed : 'down', success : 'success', required : 'required', disabled : 'disabled' }, error: { identifier : 'You must specify a string identifier for each field', method : 'The method you called is not defined.', noRule : 'There is no rule matching the one you specified', oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.', noElement : 'This module requires ui {element}' }, templates: { // template that produces error message error: function(errors) { var html = '
    ' ; $.each(errors, function(index, value) { html += '
  • ' + value + '
  • '; }); html += '
'; return $(html); }, // template that produces label prompt: function(errors, labelClasses) { return $('
') .addClass(labelClasses) .html(errors[0]) ; } }, formatter: { date: function(date) { return Intl.DateTimeFormat('en-GB').format(date); }, datetime: function(date) { return Intl.DateTimeFormat('en-GB', { year: "numeric", month: "2-digit", day: "2-digit", hour: '2-digit', minute: '2-digit', second: '2-digit' }).format(date); }, time: function(date) { return Intl.DateTimeFormat('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }).format(date); }, month: function(date) { return Intl.DateTimeFormat('en-GB', { month: '2-digit', year: 'numeric' }).format(date); }, year: function(date) { return Intl.DateTimeFormat('en-GB', { year: 'numeric' }).format(date); } }, rules: { // is not empty or blank string empty: function(value) { return !(value === undefined || '' === value || Array.isArray(value) && value.length === 0); }, // checkbox checked checked: function() { return ($(this).filter(':checked').length > 0); }, // is most likely an email email: function(value){ return $.fn.form.settings.regExp.email.test(value); }, // value is most likely url url: function(value) { return $.fn.form.settings.regExp.url.test(value); }, // matches specified regExp regExp: function(value, regExp) { if(regExp instanceof RegExp) { return value.match(regExp); } var regExpParts = regExp.match($.fn.form.settings.regExp.flags), flags ; // regular expression specified as /baz/gi (flags) if(regExpParts) { regExp = (regExpParts.length >= 2) ? regExpParts[1] : regExp ; flags = (regExpParts.length >= 3) ? regExpParts[2] : '' ; } return value.match( new RegExp(regExp, flags) ); }, minValue: function(value, range) { return $.fn.form.settings.rules.range(value, range+'..', 'number'); }, maxValue: function(value, range) { return $.fn.form.settings.rules.range(value, '..'+range, 'number'); }, // is valid integer or matches range integer: function(value, range) { return $.fn.form.settings.rules.range(value, range, 'integer'); }, range: function(value, range, regExp) { if(typeof regExp == "string") { regExp = $.fn.form.settings.regExp[regExp]; } if(!(regExp instanceof RegExp)) { regExp = $.fn.form.settings.regExp.integer; } var min, max, parts ; if( !range || ['', '..'].indexOf(range) !== -1) { // do nothing } else if(range.indexOf('..') == -1) { if(regExp.test(range)) { min = max = range - 0; } } else { parts = range.split('..', 2); if(regExp.test(parts[0])) { min = parts[0] - 0; } if(regExp.test(parts[1])) { max = parts[1] - 0; } } return ( regExp.test(value) && (min === undefined || value >= min) && (max === undefined || value <= max) ); }, // is valid number (with decimal) decimal: function(value, range) { return $.fn.form.settings.rules.range(value, range, 'decimal'); }, // is valid number number: function(value, range) { return $.fn.form.settings.rules.range(value, range, 'number'); }, // is value (case insensitive) is: function(value, text) { text = (typeof text == 'string') ? text.toLowerCase() : text ; value = (typeof value == 'string') ? value.toLowerCase() : value ; return (value == text); }, // is value isExactly: function(value, text) { return (value == text); }, // value is not another value (case insensitive) not: function(value, notValue) { value = (typeof value == 'string') ? value.toLowerCase() : value ; notValue = (typeof notValue == 'string') ? notValue.toLowerCase() : notValue ; return (value != notValue); }, // value is not another value (case sensitive) notExactly: function(value, notValue) { return (value != notValue); }, // value contains text (insensitive) contains: function(value, text) { // escape regex characters text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); return (value.search( new RegExp(text, 'i') ) !== -1); }, // value contains text (case sensitive) containsExactly: function(value, text) { // escape regex characters text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); return (value.search( new RegExp(text) ) !== -1); }, // value contains text (insensitive) doesntContain: function(value, text) { // escape regex characters text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); return (value.search( new RegExp(text, 'i') ) === -1); }, // value contains text (case sensitive) doesntContainExactly: function(value, text) { // escape regex characters text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); return (value.search( new RegExp(text) ) === -1); }, // is at least string length minLength: function(value, requiredLength) { return (value !== undefined) ? (value.length >= requiredLength) : false ; }, // see rls notes for 2.0.6 (this is a duplicate of minLength) length: function(value, requiredLength) { return (value !== undefined) ? (value.length >= requiredLength) : false ; }, // is exactly length exactLength: function(value, requiredLength) { return (value !== undefined) ? (value.length == requiredLength) : false ; }, // is less than length maxLength: function(value, maxLength) { return (value !== undefined) ? (value.length <= maxLength) : false ; }, // matches another field match: function(value, identifier, $module) { var matchingValue, matchingElement ; if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('#' + identifier)).length > 0) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { matchingValue = matchingElement; } return (matchingValue !== undefined) ? ( value.toString() == matchingValue.toString() ) : false ; }, // different than another field different: function(value, identifier, $module) { // use either id or name of field var matchingValue, matchingElement ; if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('#' + identifier)).length > 0) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { matchingValue = matchingElement.val(); } else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { matchingValue = matchingElement; } return (matchingValue !== undefined) ? ( value.toString() !== matchingValue.toString() ) : false ; }, creditCard: function(cardNumber, cardTypes) { var cards = { visa: { pattern : /^4/, length : [16] }, amex: { pattern : /^3[47]/, length : [15] }, mastercard: { pattern : /^5[1-5]/, length : [16] }, discover: { pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/, length : [16] }, unionPay: { pattern : /^(62|88)/, length : [16, 17, 18, 19] }, jcb: { pattern : /^35(2[89]|[3-8][0-9])/, length : [16] }, maestro: { pattern : /^(5018|5020|5038|6304|6759|676[1-3])/, length : [12, 13, 14, 15, 16, 17, 18, 19] }, dinersClub: { pattern : /^(30[0-5]|^36)/, length : [14] }, laser: { pattern : /^(6304|670[69]|6771)/, length : [16, 17, 18, 19] }, visaElectron: { pattern : /^(4026|417500|4508|4844|491(3|7))/, length : [16] } }, valid = {}, validCard = false, requiredTypes = (typeof cardTypes == 'string') ? cardTypes.split(',') : false, unionPay, validation ; if(typeof cardNumber !== 'string' || cardNumber.length === 0) { return; } // allow dashes and spaces in card cardNumber = cardNumber.replace(/[\s\-]/g, ''); // verify card types if(requiredTypes) { $.each(requiredTypes, function(index, type){ // verify each card type validation = cards[type]; if(validation) { valid = { length : ($.inArray(cardNumber.length, validation.length) !== -1), pattern : (cardNumber.search(validation.pattern) !== -1) }; if(valid.length && valid.pattern) { validCard = true; } } }); if(!validCard) { return false; } } // skip luhn for UnionPay unionPay = { number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1), pattern : (cardNumber.search(cards.unionPay.pattern) !== -1) }; if(unionPay.number && unionPay.pattern) { return true; } // verify luhn, adapted from var length = cardNumber.length, multiple = 0, producedValue = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] ], sum = 0 ; while (length--) { sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)]; multiple ^= 1; } return (sum % 10 === 0 && sum > 0); }, minCount: function(value, minCount) { if(minCount == 0) { return true; } if(minCount == 1) { return (value !== ''); } return (value.split(',').length >= minCount); }, exactCount: function(value, exactCount) { if(exactCount == 0) { return (value === ''); } if(exactCount == 1) { return (value !== '' && value.search(',') === -1); } return (value.split(',').length == exactCount); }, maxCount: function(value, maxCount) { if(maxCount == 0) { return false; } if(maxCount == 1) { return (value.search(',') === -1); } return (value.split(',').length <= maxCount); } } }; })( jQuery, window, document ); /*! * # Fomantic-UI 2.8.8 - Modal * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) ? self : Function('return this')() ; $.fn.modal = function(parameters) { var $allModules = $(this), $window = $(window), $document = $(document), $body = $('body'), moduleSelector = $allModules.selector || '', time = new Date().getTime(), performance = [], query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [].slice.call(arguments, 1), requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { setTimeout(callback, 0); }, returnedValue ; $allModules .each(function() { var settings = ( $.isPlainObject(parameters) ) ? $.extend(true, {}, $.fn.modal.settings, parameters) : $.extend({}, $.fn.modal.settings), selector = settings.selector, className = settings.className, namespace = settings.namespace, fields = settings.fields, error = settings.error, eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace, $module = $(this), $context = $(settings.context), $close = $module.find(selector.close), $allModals, $otherModals, $focusedElement, $dimmable, $dimmer, element = this, instance = $module.hasClass('modal') ? $module.data(moduleNamespace) : undefined, ignoreRepeatedEvents = false, initialMouseDownInModal, initialMouseDownInScrollbar, initialBodyMargin = '', tempBodyMargin = '', elementEventNamespace, id, observer, module ; module = { initialize: function() { if(!$module.hasClass('modal')) { module.create.modal(); if(!$.isFunction(settings.onHidden)) { settings.onHidden = function () { module.destroy(); $module.remove(); }; } } $module.addClass(settings.class); if (settings.title !== '') { $module.find(selector.title).html(module.helpers.escape(settings.title, settings.preserveHTML)).addClass(settings.classTitle); } if (settings.content !== '') { $module.find(selector.content).html(module.helpers.escape(settings.content, settings.preserveHTML)).addClass(settings.classContent); } if(module.has.configActions()){ var $actions = $module.find(selector.actions).addClass(settings.classActions); if ($actions.length === 0) { $actions = $('
', {class: className.actions + ' ' + (settings.classActions || '')}).appendTo($module); } else { $actions.empty(); } settings.actions.forEach(function (el) { var icon = el[fields.icon] ? '' : '', text = module.helpers.escape(el[fields.text] || '', settings.preserveHTML), cls = module.helpers.deQuote(el[fields.class] || ''), click = el[fields.click] && $.isFunction(el[fields.click]) ? el[fields.click] : function () {}; $actions.append($('