From e05fae910849b76dd1ff6fc288607ba4b480a719 Mon Sep 17 00:00:00 2001 From: spirosdi Date: Wed, 29 Oct 2014 16:53:37 +0100 Subject: [PATCH 1/3] Added triggering the change event on the select Added triggering the change event on the select element when the selected is updated --- jquery.fs.selecter.js | 2495 ++++++++++++++++++++++++------------- src/jquery.fs.selecter.js | 2 + 2 files changed, 1665 insertions(+), 832 deletions(-) diff --git a/jquery.fs.selecter.js b/jquery.fs.selecter.js index 69081bc..dd33ce1 100644 --- a/jquery.fs.selecter.js +++ b/jquery.fs.selecter.js @@ -6,846 +6,1677 @@ * Copyright 2014 Ben Plum; MIT Licensed */ -;(function ($, window) { - "use strict"; - - var guid = 0, - userAgent = (window.navigator.userAgent||window.navigator.vendor||window.opera), - isFirefox = /Firefox/i.test(userAgent), - isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent), - isFirefoxMobile = (isFirefox && isMobile), - $body = null; - - /** - * @options - * @param callback [function] <$.noop> "Select item callback" - * @param cover [boolean] "Cover handle with option set" - * @param customClass [string] <''> "Class applied to instance" - * @param label [string] <''> "Label displayed before selection" - * @param external [boolean] "Open options as links in new window" - * @param links [boolean] "Open options as links in same window" - * @param mobile [boolean] "Force desktop interaction on mobile" - * @param trim [int] <0> "Trim options to specified length; 0 to disable” - */ - var options = { - callback: $.noop, - cover: false, - customClass: "", - label: "", - external: false, - links: false, - mobile: false, - trim: 0 - }; - - var pub = { - - /** - * @method - * @name defaults - * @description Sets default plugin options - * @param opts [object] <{}> "Options object" - * @example $.selecter("defaults", opts); - */ - defaults: function(opts) { - options = $.extend(options, opts || {}); - return $(this); - }, - - /** - * @method - * @name disable - * @description Disables target instance or option - * @param option [string] "Target option value" - * @example $(".target").selecter("disable", "1"); - */ - disable: function(option) { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (typeof option !== "undefined") { - var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); - - data.$items.eq(index).addClass("disabled"); - data.$options.eq(index).prop("disabled", true); - } else { - if (data.$selecter.hasClass("open")) { - data.$selecter.find(".selecter-selected").trigger("click.selecter"); - } - - data.$selecter.addClass("disabled"); - data.$select.prop("disabled", true); - } - } - }); - }, - - /** - * @method - * @name destroy - * @description Removes instance of plugin - * @example $(".target").selecter("destroy"); - */ - destroy: function() { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (data.$selecter.hasClass("open")) { - data.$selecter.find(".selecter-selected").trigger("click.selecter"); - } - - // Scroller support - if ($.fn.scroller !== undefined) { - data.$selecter.find(".selecter-options").scroller("destroy"); - } - - data.$select[0].tabIndex = data.tabIndex; - - data.$select.find(".selecter-placeholder").remove(); - data.$selected.remove(); - data.$itemsWrapper.remove(); - - data.$selecter.off(".selecter"); - - data.$select.off(".selecter") - .removeClass("selecter-element") - .show() - .unwrap(); - } - }); - }, - - /** - * @method - * @name enable - * @description Enables target instance or option - * @param option [string] "Target option value" - * @example $(".target").selecter("enable", "1"); - */ - enable: function(option) { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (typeof option !== "undefined") { - var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); - data.$items.eq(index).removeClass("disabled"); - data.$options.eq(index).prop("disabled", false); - } else { - data.$selecter.removeClass("disabled"); - data.$select.prop("disabled", false); - } - } - }); - }, - - - /** - * @method private - * @name refresh - * @description DEPRECATED - Updates instance base on target options - * @example $(".target").selecter("refresh"); - */ - refresh: function() { - return pub.update.apply($(this)); - }, - - /** - * @method - * @name update - * @description Updates instance base on target options - * @example $(".target").selecter("update"); - */ - update: function() { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - var index = data.index; - - data.$allOptions = data.$select.find("option, optgroup"); - data.$options = data.$allOptions.filter("option"); - data.index = -1; - - index = data.$options.index(data.$options.filter(":selected")); - - _buildOptions(data); - - if (!data.multiple) { - _update(index, data); - } - } - }); - } - }; - - /** - * @method private - * @name _init - * @description Initializes plugin - * @param opts [object] "Initialization options" - */ - function _init(opts) { - // Local options - opts = $.extend({}, options, opts || {}); - - // Check for Body - if ($body === null) { - $body = $("body"); - } - - // Apply to each element - var $items = $(this); - for (var i = 0, count = $items.length; i < count; i++) { - _build($items.eq(i), opts); - } - return $items; - } - - /** - * @method private - * @name _build - * @description Builds each instance - * @param $select [jQuery object] "Target jQuery object" - * @param opts [object] <{}> "Options object" - */ - function _build($select, opts) { - if (!$select.hasClass("selecter-element")) { - // EXTEND OPTIONS - opts = $.extend({}, opts, $select.data("selecter-options")); - - opts.multiple = $select.prop("multiple"); - opts.disabled = $select.is(":disabled"); - - if (opts.external) { - opts.links = true; +;(function ($, window) { + + "use strict"; + + + + var guid = 0, + + userAgent = (window.navigator.userAgent||window.navigator.vendor||window.opera), + + isFirefox = /Firefox/i.test(userAgent), + + isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent), + + isFirefoxMobile = (isFirefox && isMobile), + + $body = null; + + + + /** + + * @options + + * @param callback [function] <$.noop> "Select item callback" + + * @param cover [boolean] "Cover handle with option set" + + * @param customClass [string] <''> "Class applied to instance" + + * @param label [string] <''> "Label displayed before selection" + + * @param external [boolean] "Open options as links in new window" + + * @param links [boolean] "Open options as links in same window" + + * @param mobile [boolean] "Force desktop interaction on mobile" + + * @param trim [int] <0> "Trim options to specified length; 0 to disable” + + */ + + var options = { + + callback: $.noop, + + cover: false, + + customClass: "", + + label: "", + + external: false, + + links: false, + + mobile: false, + + trim: 0 + + }; + + + + var pub = { + + + + /** + + * @method + + * @name defaults + + * @description Sets default plugin options + + * @param opts [object] <{}> "Options object" + + * @example $.selecter("defaults", opts); + + */ + + defaults: function(opts) { + + options = $.extend(options, opts || {}); + + return $(this); + + }, + + + + /** + + * @method + + * @name disable + + * @description Disables target instance or option + + * @param option [string] "Target option value" + + * @example $(".target").selecter("disable", "1"); + + */ + + disable: function(option) { + + return $(this).each(function(i, input) { + + var data = $(input).parent(".selecter").data("selecter"); + + + + if (data) { + + if (typeof option !== "undefined") { + + var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); + + + + data.$items.eq(index).addClass("disabled"); + + data.$options.eq(index).prop("disabled", true); + + } else { + + if (data.$selecter.hasClass("open")) { + + data.$selecter.find(".selecter-selected").trigger("click.selecter"); + + } + + + + data.$selecter.addClass("disabled"); + + data.$select.prop("disabled", true); + + } + + } + + }); + + }, + + + + /** + + * @method + + * @name destroy + + * @description Removes instance of plugin + + * @example $(".target").selecter("destroy"); + + */ + + destroy: function() { + + return $(this).each(function(i, input) { + + var data = $(input).parent(".selecter").data("selecter"); + + + + if (data) { + + if (data.$selecter.hasClass("open")) { + + data.$selecter.find(".selecter-selected").trigger("click.selecter"); + + } + + + + // Scroller support + + if ($.fn.scroller !== undefined) { + + data.$selecter.find(".selecter-options").scroller("destroy"); + + } + + + + data.$select[0].tabIndex = data.tabIndex; + + + + data.$select.find(".selecter-placeholder").remove(); + + data.$selected.remove(); + + data.$itemsWrapper.remove(); + + + + data.$selecter.off(".selecter"); + + + + data.$select.off(".selecter") + + .removeClass("selecter-element") + + .show() + + .unwrap(); + + } + + }); + + }, + + + + /** + + * @method + + * @name enable + + * @description Enables target instance or option + + * @param option [string] "Target option value" + + * @example $(".target").selecter("enable", "1"); + + */ + + enable: function(option) { + + return $(this).each(function(i, input) { + + var data = $(input).parent(".selecter").data("selecter"); + + + + if (data) { + + if (typeof option !== "undefined") { + + var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); + + data.$items.eq(index).removeClass("disabled"); + + data.$options.eq(index).prop("disabled", false); + + } else { + + data.$selecter.removeClass("disabled"); + + data.$select.prop("disabled", false); + + } + + } + + }); + + }, + + + + + + /** + + * @method private + + * @name refresh + + * @description DEPRECATED - Updates instance base on target options + + * @example $(".target").selecter("refresh"); + + */ + + refresh: function() { + + return pub.update.apply($(this)); + + }, + + + + /** + + * @method + + * @name update + + * @description Updates instance base on target options + + * @example $(".target").selecter("update"); + + */ + + update: function() { + + return $(this).each(function(i, input) { + + var data = $(input).parent(".selecter").data("selecter"); + + + + if (data) { + + var index = data.index; + + + + data.$allOptions = data.$select.find("option, optgroup"); + + data.$options = data.$allOptions.filter("option"); + + data.index = -1; + + + + index = data.$options.index(data.$options.filter(":selected")); + + + + _buildOptions(data); + + + + if (!data.multiple) { + + _update(index, data); + + } + + } + + }); + + } + + }; + + + + /** + + * @method private + + * @name _init + + * @description Initializes plugin + + * @param opts [object] "Initialization options" + + */ + + function _init(opts) { + + // Local options + + opts = $.extend({}, options, opts || {}); + + + + // Check for Body + + if ($body === null) { + + $body = $("body"); + + } + + + + // Apply to each element + + var $items = $(this); + + for (var i = 0, count = $items.length; i < count; i++) { + + _build($items.eq(i), opts); + + } + + return $items; + + } + + + + /** + + * @method private + + * @name _build + + * @description Builds each instance + + * @param $select [jQuery object] "Target jQuery object" + + * @param opts [object] <{}> "Options object" + + */ + + function _build($select, opts) { + + if (!$select.hasClass("selecter-element")) { + + // EXTEND OPTIONS + + opts = $.extend({}, opts, $select.data("selecter-options")); + + + + opts.multiple = $select.prop("multiple"); + + opts.disabled = $select.is(":disabled"); + + + + if (opts.external) { + + opts.links = true; + } // Grab true original index, only if selected attribute exits var $originalOption = $select.find("[selected]").not(":disabled"), originalOptionIndex = $select.find("option").index($originalOption); - - if (!opts.multiple && opts.label !== "") { + + + if (!opts.multiple && opts.label !== "") { + $select.prepend(''); if (originalOptionIndex > -1) { originalOptionIndex++; - } - } else { - opts.label = ""; - } - - // Build options array - var $allOptions = $select.find("option, optgroup"), - $options = $allOptions.filter("option"); + } + + } else { + + opts.label = ""; - // If we didn't actually have a selected elemtn - if (!$originalOption.length) { - $originalOption = $options.eq(0); } - // Determine original item - var originalIndex = (originalOptionIndex > -1) ? originalOptionIndex : 0, - originalLabel = (opts.label !== "") ? opts.label : $originalOption.text(), - wrapperTag = "div"; - - // Swap tab index, no more interacting with the actual select! - opts.tabIndex = $select[0].tabIndex; - $select[0].tabIndex = -1; - - // Build HTML - var inner = "", - wrapper = ""; - - // Build wrapper - wrapper += '<' + wrapperTag + ' class="selecter ' + opts.customClass; - // Special case classes - if (isMobile) { - wrapper += ' mobile'; - } else if (opts.cover) { - wrapper += ' cover'; - } - if (opts.multiple) { - wrapper += ' multiple'; - } else { - wrapper += ' closed'; - } - if (opts.disabled) { - wrapper += ' disabled'; - } - wrapper += '" tabindex="' + opts.tabIndex + '">'; - wrapper += ''; - - // Build inner - if (!opts.multiple) { - inner += ''; - inner += $('').text( _trim(originalLabel, opts.trim) ).html(); - inner += ''; - } - inner += '
'; - inner += '
'; - - // Modify DOM - $select.addClass("selecter-element") - .wrap(wrapper) - .after(inner); - - // Store plugin data - var $selecter = $select.parent(".selecter"), - data = $.extend({ - $select: $select, - $allOptions: $allOptions, - $options: $options, - $selecter: $selecter, - $selected: $selecter.find(".selecter-selected"), - $itemsWrapper: $selecter.find(".selecter-options"), - index: -1, - guid: guid++ - }, opts); - - _buildOptions(data); - - if (!data.multiple) { - _update(originalIndex, data); - } - - // Scroller support - if ($.fn.scroller !== undefined) { - data.$itemsWrapper.scroller(); - } - - // Bind click events - data.$selecter.on("touchstart.selecter", ".selecter-selected", data, _onTouchStart) - .on("click.selecter", ".selecter-selected", data, _onClick) - .on("click.selecter", ".selecter-item", data, _onSelect) - .on("close.selecter", data, _onClose) - .data("selecter", data); - - // Change events - data.$select.on("change.selecter", data, _onChange); - - // Focus/Blur events - if (!isMobile) { - data.$selecter.on("focusin.selecter", data, _onFocus) - .on("blur.selecter", data, _onBlur); - - // Handle clicks to associated labels - data.$select.on("focusin.selecter", data, function(e) { - e.data.$selecter.trigger("focus"); - }); - } - } - } - - /** - * @method private - * @name _buildOptions - * @description Builds instance's option set - * @param data [object] "Instance data" - */ - function _buildOptions(data) { - var html = '', - itemTag = (data.links) ? "a" : "span", - j = 0; - - for (var i = 0, count = data.$allOptions.length; i < count; i++) { - var $op = data.$allOptions.eq(i); - - // Option group - if ($op[0].tagName === "OPTGROUP") { - html += ''; - } else { - var opVal = $op.val(); - - if (!$op.attr("value")) { - $op.attr("value", opVal); - } - - html += '<' + itemTag + ' class="selecter-item'; - if ($op.hasClass('selecter-placeholder')) { - html += ' placeholder'; - } - // Default selected value - now handles multi's thanks to @kuilkoff - if ($op.is(':selected')) { - html += ' selected'; - } - // Disabled options - if ($op.is(":disabled")) { - html += ' disabled'; - } - html += '" '; - if (data.links) { - html += 'href="' + opVal + '"'; - } else { - html += 'data-value="' + opVal + '"'; - } - html += '>' + $("").text( _trim($op.text(), data.trim) ).html() + ''; - j++; - } - } - - data.$itemsWrapper.html(html); - data.$items = data.$selecter.find(".selecter-item"); - } - - /** - * @method private - * @name _onTouchStart - * @description Handles touchstart to selected item - * @param e [object] "Event data" - */ - function _onTouchStart(e) { - e.stopPropagation(); - - var data = e.data; - - data.touchStartEvent = e.originalEvent; - - data.touchStartX = data.touchStartEvent.touches[0].clientX; - data.touchStartY = data.touchStartEvent.touches[0].clientY; - - data.$selecter.on("touchmove.selecter", ".selecter-selected", data, _onTouchMove) - .on("touchend.selecter", ".selecter-selected", data, _onTouchEnd); - } - - /** - * @method private - * @name _onTouchMove - * @description Handles touchmove to selected item - * @param e [object] "Event data" - */ - function _onTouchMove(e) { - var data = e.data, - oe = e.originalEvent; - - if (Math.abs(oe.touches[0].clientX - data.touchStartX) > 10 || Math.abs(oe.touches[0].clientY - data.touchStartY) > 10) { - data.$selecter.off("touchmove.selecter touchend.selecter"); - } - } - - /** - * @method private - * @name _onTouchEnd - * @description Handles touchend to selected item - * @param e [object] "Event data" - */ - function _onTouchEnd(e) { - var data = e.data; - - data.touchStartEvent.preventDefault(); - - data.$selecter.off("touchmove.selecter touchend.selecter"); - - _onClick(e); - } - - /** - * @method private - * @name _onClick - * @description Handles click to selected item - * @param e [object] "Event data" - */ - function _onClick(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - if (!data.$select.is(":disabled")) { - $(".selecter").not(data.$selecter).trigger("close.selecter", [data]); - - // Handle mobile, but not Firefox, unless desktop forced - if (!data.mobile && isMobile && !isFirefoxMobile) { - var el = data.$select[0]; - if (window.document.createEvent) { // All - var evt = window.document.createEvent("MouseEvents"); - evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - el.dispatchEvent(evt); - } else if (el.fireEvent) { // IE - el.fireEvent("onmousedown"); - } - } else { - // Delegate intent - if (data.$selecter.hasClass("closed")) { - _onOpen(e); - } else if (data.$selecter.hasClass("open")) { - _onClose(e); - } - } - } - } - - /** - * @method private - * @name _onOpen - * @description Opens option set - * @param e [object] "Event data" - */ - function _onOpen(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - // Make sure it's not alerady open - if (!data.$selecter.hasClass("open")) { - var offset = data.$selecter.offset(), - bodyHeight = $body.outerHeight(), - optionsHeight = data.$itemsWrapper.outerHeight(true), - selectedOffset = (data.index >= 0) ? data.$items.eq(data.index).position() : { left: 0, top: 0 }; - - // Calculate bottom of document - if (offset.top + optionsHeight > bodyHeight) { - data.$selecter.addClass("bottom"); - } - - data.$itemsWrapper.show(); - - // Bind Events - data.$selecter.removeClass("closed") - .addClass("open"); - $body.on("click.selecter-" + data.guid, ":not(.selecter-options)", data, _onCloseHelper); - - _scrollOptions(data); - } - } - - /** - * @method private - * @name _onCloseHelper - * @description Determines if event target is outside instance before closing - * @param e [object] "Event data" - */ - function _onCloseHelper(e) { - e.preventDefault(); - e.stopPropagation(); - - if ($(e.currentTarget).parents(".selecter").length === 0) { - _onClose(e); - } - } - - /** - * @method private - * @name _onClose - * @description Closes option set - * @param e [object] "Event data" - */ - function _onClose(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - // Make sure it's actually open - if (data.$selecter.hasClass("open")) { - data.$itemsWrapper.hide(); - data.$selecter.removeClass("open bottom") - .addClass("closed"); - - $body.off(".selecter-" + data.guid); - } - } - - /** - * @method private - * @name _onSelect - * @description Handles option select - * @param e [object] "Event data" - */ - function _onSelect(e) { - e.preventDefault(); - e.stopPropagation(); - - var $target = $(this), - data = e.data; - - if (!data.$select.is(":disabled")) { - if (data.$itemsWrapper.is(":visible")) { - // Update - var index = data.$items.index($target); - - if (index !== data.index) { - _update(index, data); - _handleChange(data); - } - } - - if (!data.multiple) { - // Clean up - _onClose(e); - } - } - } - - /** - * @method private - * @name _onChange - * @description Handles external changes - * @param e [object] "Event data" - */ - function _onChange(e, internal) { - var $target = $(this), - data = e.data; - - if (!internal && !data.multiple) { - var index = data.$options.index(data.$options.filter("[value='" + _escape($target.val()) + "']")); - - _update(index, data); - _handleChange(data); - } - } - - /** - * @method private - * @name _onFocus - * @description Handles instance focus - * @param e [object] "Event data" - */ - function _onFocus(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - if (!data.$select.is(":disabled") && !data.multiple) { - data.$selecter.addClass("focus") - .on("keydown.selecter-" + data.guid, data, _onKeypress); - - $(".selecter").not(data.$selecter) - .trigger("close.selecter", [ data ]); - } - } - - /** - * @method private - * @name _onBlur - * @description Handles instance focus - * @param e [object] "Event data" - */ - function _onBlur(e, internal, two) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - data.$selecter.removeClass("focus") - .off("keydown.selecter-" + data.guid); - - $(".selecter").not(data.$selecter) - .trigger("close.selecter", [ data ]); - } - - /** - * @method private - * @name _onKeypress - * @description Handles instance keypress, once focused - * @param e [object] "Event data" - */ - function _onKeypress(e) { - var data = e.data; - - if (e.keyCode === 13) { - if (data.$selecter.hasClass("open")) { - _onClose(e); - _update(data.index, data); - } - _handleChange(data); - } else if (e.keyCode !== 9 && (!e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)) { - // Ignore modifiers & tabs - e.preventDefault(); - e.stopPropagation(); - - var total = data.$items.length - 1, - index = (data.index < 0) ? 0 : data.index; - - // Firefox left/right support thanks to Kylemade - if ($.inArray(e.keyCode, (isFirefox) ? [38, 40, 37, 39] : [38, 40]) > -1) { - // Increment / decrement using the arrow keys - index = index + ((e.keyCode === 38 || (isFirefox && e.keyCode === 37)) ? -1 : 1); - - if (index < 0) { - index = 0; - } - if (index > total) { - index = total; - } - } else { - var input = String.fromCharCode(e.keyCode).toUpperCase(), - letter, - i; - - // Search for input from original index - for (i = data.index + 1; i <= total; i++) { - letter = data.$options.eq(i).text().charAt(0).toUpperCase(); - if (letter === input) { - index = i; - break; - } - } - - // If not, start from the beginning - if (index < 0 || index === data.index) { - for (i = 0; i <= total; i++) { - letter = data.$options.eq(i).text().charAt(0).toUpperCase(); - if (letter === input) { - index = i; - break; - } - } - } - } - - // Update - if (index >= 0) { - _update(index, data); - _scrollOptions(data); - } - } - } - - /** - * @method private - * @name _update - * @description Updates instance based on new target index - * @param index [int] "Selected option index" - * @param data [object] "instance data" - */ - function _update(index, data) { - var $item = data.$items.eq(index), - isSelected = $item.hasClass("selected"), - isDisabled = $item.hasClass("disabled"); - - // Check for disabled options - if (!isDisabled) { - if (data.multiple) { - if (isSelected) { - data.$options.eq(index).prop("selected", null); - $item.removeClass("selected"); - } else { - data.$options.eq(index).prop("selected", true); - $item.addClass("selected"); - } - } else if (index > -1 && index < data.$items.length) { - var newLabel = $item.html(), - newValue = $item.data("value"); - - data.$selected.html(newLabel) - .removeClass('placeholder'); - - data.$items.filter(".selected") - .removeClass("selected"); - - data.$select[0].selectedIndex = index; - - $item.addClass("selected"); - data.index = index; - } else if (data.label !== "") { - data.$selected.html(data.label); - } - } - } - - /** - * @method private - * @name _scrollOptions - * @description Scrolls options wrapper to specific option - * @param data [object] "Instance data" - */ - function _scrollOptions(data) { - var $selected = data.$items.eq(data.index), - selectedOffset = (data.index >= 0 && !$selected.hasClass("placeholder")) ? $selected.position() : { left: 0, top: 0 }; - - if ($.fn.scroller !== undefined) { - data.$itemsWrapper.scroller("scroll", (data.$itemsWrapper.find(".scroller-content").scrollTop() + selectedOffset.top), 0) - .scroller("reset"); - } else { - data.$itemsWrapper.scrollTop( data.$itemsWrapper.scrollTop() + selectedOffset.top ); - } - } - - /** - * @method private - * @name _handleChange - * @description Handles change events - * @param data [object] "Instance data" - */ - function _handleChange(data) { - if (data.links) { - _launch(data); - } else { - data.callback.call(data.$selecter, data.$select.val(), data.index); - data.$select.trigger("change", [ true ]); - } - } - - /** - * @method private - * @name _launch - * @description Launches link - * @param data [object] "Instance data" - */ - function _launch(data) { - //var url = (isMobile) ? data.$select.val() : data.$options.filter(":selected").attr("href"); - var url = data.$select.val(); - - if (data.external) { - // Open link in a new tab/window - window.open(url); - } else { - // Open link in same tab/window - window.location.href = url; - } - } - - /** - * @method private - * @name _trim - * @description Trims text, if specified length is greater then 0 - * @param length [int] "Length to trim at" - * @param text [string] "Text to trim" - * @return [string] "Trimmed string" - */ - function _trim(text, length) { - if (length === 0) { - return text; - } else { - if (text.length > length) { - return text.substring(0, length) + "..."; - } else { - return text; - } - } - } - - /** - * @method private - * @name _escape - * @description Escapes text - * @param text [string] "Text to escape" - */ - function _escape(text) { - return (typeof text === "string") ? text.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1') : text; - } - - $.fn.selecter = function(method) { - if (pub[method]) { - return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method) { - return _init.apply(this, arguments); - } - return this; - }; - - $.selecter = function(method) { - if (method === "defaults") { - pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); - } - }; -})(jQuery, window); + + + // Build options array + + var $allOptions = $select.find("option, optgroup"), + + $options = $allOptions.filter("option"); + + // If we didn't actually have a selected elemtn + if (!$originalOption.length) { + $originalOption = $options.eq(0); + } + + // Determine original item + var originalIndex = (originalOptionIndex > -1) ? originalOptionIndex : 0, + + originalLabel = (opts.label !== "") ? opts.label : $originalOption.text(), + + wrapperTag = "div"; + + + + // Swap tab index, no more interacting with the actual select! + + opts.tabIndex = $select[0].tabIndex; + + $select[0].tabIndex = -1; + + + + // Build HTML + + var inner = "", + + wrapper = ""; + + + + // Build wrapper + + wrapper += '<' + wrapperTag + ' class="selecter ' + opts.customClass; + + // Special case classes + + if (isMobile) { + + wrapper += ' mobile'; + + } else if (opts.cover) { + + wrapper += ' cover'; + + } + + if (opts.multiple) { + + wrapper += ' multiple'; + + } else { + + wrapper += ' closed'; + + } + + if (opts.disabled) { + + wrapper += ' disabled'; + + } + + wrapper += '" tabindex="' + opts.tabIndex + '">'; + + wrapper += ''; + + + + // Build inner + + if (!opts.multiple) { + + inner += ''; + + inner += $('').text( _trim(originalLabel, opts.trim) ).html(); + + inner += ''; + + } + + inner += '
'; + + inner += '
'; + + + + // Modify DOM + + $select.addClass("selecter-element") + + .wrap(wrapper) + + .after(inner); + + + + // Store plugin data + + var $selecter = $select.parent(".selecter"), + + data = $.extend({ + + $select: $select, + + $allOptions: $allOptions, + + $options: $options, + + $selecter: $selecter, + + $selected: $selecter.find(".selecter-selected"), + + $itemsWrapper: $selecter.find(".selecter-options"), + + index: -1, + + guid: guid++ + + }, opts); + + + + _buildOptions(data); + + + + if (!data.multiple) { + + _update(originalIndex, data); + + } + + + + // Scroller support + + if ($.fn.scroller !== undefined) { + + data.$itemsWrapper.scroller(); + + } + + + + // Bind click events + + data.$selecter.on("touchstart.selecter", ".selecter-selected", data, _onTouchStart) + + .on("click.selecter", ".selecter-selected", data, _onClick) + + .on("click.selecter", ".selecter-item", data, _onSelect) + + .on("close.selecter", data, _onClose) + + .data("selecter", data); + + + + // Change events + + data.$select.on("change.selecter", data, _onChange); + + + + // Focus/Blur events + + if (!isMobile) { + + data.$selecter.on("focusin.selecter", data, _onFocus) + + .on("blur.selecter", data, _onBlur); + + + + // Handle clicks to associated labels + + data.$select.on("focusin.selecter", data, function(e) { + + e.data.$selecter.trigger("focus"); + + }); + + } + + } + + } + + + + /** + + * @method private + + * @name _buildOptions + + * @description Builds instance's option set + + * @param data [object] "Instance data" + + */ + + function _buildOptions(data) { + + var html = '', + + itemTag = (data.links) ? "a" : "span", + + j = 0; + + + + for (var i = 0, count = data.$allOptions.length; i < count; i++) { + + var $op = data.$allOptions.eq(i); + + + + // Option group + + if ($op[0].tagName === "OPTGROUP") { + + html += ''; + + } else { + + var opVal = $op.val(); + + + + if (!$op.attr("value")) { + + $op.attr("value", opVal); + + } + + + + html += '<' + itemTag + ' class="selecter-item'; + + if ($op.hasClass('selecter-placeholder')) { + + html += ' placeholder'; + + } + + // Default selected value - now handles multi's thanks to @kuilkoff + + if ($op.is(':selected')) { + + html += ' selected'; + + } + + // Disabled options + + if ($op.is(":disabled")) { + + html += ' disabled'; + + } + + html += '" '; + + if (data.links) { + + html += 'href="' + opVal + '"'; + + } else { + + html += 'data-value="' + opVal + '"'; + + } + + html += '>' + $("").text( _trim($op.text(), data.trim) ).html() + ''; + + j++; + + } + + } + + + + data.$itemsWrapper.html(html); + + data.$items = data.$selecter.find(".selecter-item"); + + } + + + + /** + + * @method private + + * @name _onTouchStart + + * @description Handles touchstart to selected item + + * @param e [object] "Event data" + + */ + + function _onTouchStart(e) { + + e.stopPropagation(); + + + + var data = e.data; + + + + data.touchStartEvent = e.originalEvent; + + + + data.touchStartX = data.touchStartEvent.touches[0].clientX; + + data.touchStartY = data.touchStartEvent.touches[0].clientY; + + + + data.$selecter.on("touchmove.selecter", ".selecter-selected", data, _onTouchMove) + + .on("touchend.selecter", ".selecter-selected", data, _onTouchEnd); + + } + + + + /** + + * @method private + + * @name _onTouchMove + + * @description Handles touchmove to selected item + + * @param e [object] "Event data" + + */ + + function _onTouchMove(e) { + + var data = e.data, + + oe = e.originalEvent; + + + + if (Math.abs(oe.touches[0].clientX - data.touchStartX) > 10 || Math.abs(oe.touches[0].clientY - data.touchStartY) > 10) { + + data.$selecter.off("touchmove.selecter touchend.selecter"); + + } + + } + + + + /** + + * @method private + + * @name _onTouchEnd + + * @description Handles touchend to selected item + + * @param e [object] "Event data" + + */ + + function _onTouchEnd(e) { + + var data = e.data; + + + + data.touchStartEvent.preventDefault(); + + + + data.$selecter.off("touchmove.selecter touchend.selecter"); + + + + _onClick(e); + + } + + + + /** + + * @method private + + * @name _onClick + + * @description Handles click to selected item + + * @param e [object] "Event data" + + */ + + function _onClick(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var data = e.data; + + + + if (!data.$select.is(":disabled")) { + + $(".selecter").not(data.$selecter).trigger("close.selecter", [data]); + + + + // Handle mobile, but not Firefox, unless desktop forced + + if (!data.mobile && isMobile && !isFirefoxMobile) { + + var el = data.$select[0]; + + if (window.document.createEvent) { // All + + var evt = window.document.createEvent("MouseEvents"); + + evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + + el.dispatchEvent(evt); + + } else if (el.fireEvent) { // IE + + el.fireEvent("onmousedown"); + + } + + } else { + + // Delegate intent + + if (data.$selecter.hasClass("closed")) { + + _onOpen(e); + + } else if (data.$selecter.hasClass("open")) { + + _onClose(e); + + } + + } + + } + + } + + + + /** + + * @method private + + * @name _onOpen + + * @description Opens option set + + * @param e [object] "Event data" + + */ + + function _onOpen(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var data = e.data; + + + + // Make sure it's not alerady open + + if (!data.$selecter.hasClass("open")) { + + var offset = data.$selecter.offset(), + + bodyHeight = $body.outerHeight(), + + optionsHeight = data.$itemsWrapper.outerHeight(true), + + selectedOffset = (data.index >= 0) ? data.$items.eq(data.index).position() : { left: 0, top: 0 }; + + + + // Calculate bottom of document + + if (offset.top + optionsHeight > bodyHeight) { + + data.$selecter.addClass("bottom"); + + } + + + + data.$itemsWrapper.show(); + + + + // Bind Events + + data.$selecter.removeClass("closed") + + .addClass("open"); + + $body.on("click.selecter-" + data.guid, ":not(.selecter-options)", data, _onCloseHelper); + + + + _scrollOptions(data); + + } + + } + + + + /** + + * @method private + + * @name _onCloseHelper + + * @description Determines if event target is outside instance before closing + + * @param e [object] "Event data" + + */ + + function _onCloseHelper(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + if ($(e.currentTarget).parents(".selecter").length === 0) { + + _onClose(e); + + } + + } + + + + /** + + * @method private + + * @name _onClose + + * @description Closes option set + + * @param e [object] "Event data" + + */ + + function _onClose(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var data = e.data; + + + + // Make sure it's actually open + + if (data.$selecter.hasClass("open")) { + + data.$itemsWrapper.hide(); + + data.$selecter.removeClass("open bottom") + + .addClass("closed"); + + + + $body.off(".selecter-" + data.guid); + + } + + } + + + + /** + + * @method private + + * @name _onSelect + + * @description Handles option select + + * @param e [object] "Event data" + + */ + + function _onSelect(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var $target = $(this), + + data = e.data; + + + + if (!data.$select.is(":disabled")) { + + if (data.$itemsWrapper.is(":visible")) { + + // Update + + var index = data.$items.index($target); + + + + if (index !== data.index) { + + _update(index, data); + + _handleChange(data); + + } + + } + + + + if (!data.multiple) { + + // Clean up + + _onClose(e); + + } + + } + + } + + + + /** + + * @method private + + * @name _onChange + + * @description Handles external changes + + * @param e [object] "Event data" + + */ + + function _onChange(e, internal) { + + var $target = $(this), + + data = e.data; + + + + if (!internal && !data.multiple) { + + var index = data.$options.index(data.$options.filter("[value='" + _escape($target.val()) + "']")); + + + + _update(index, data); + + _handleChange(data); + + } + + } + + + + /** + + * @method private + + * @name _onFocus + + * @description Handles instance focus + + * @param e [object] "Event data" + + */ + + function _onFocus(e) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var data = e.data; + + + + if (!data.$select.is(":disabled") && !data.multiple) { + + data.$selecter.addClass("focus") + + .on("keydown.selecter-" + data.guid, data, _onKeypress); + + + + $(".selecter").not(data.$selecter) + + .trigger("close.selecter", [ data ]); + + } + + } + + + + /** + + * @method private + + * @name _onBlur + + * @description Handles instance focus + + * @param e [object] "Event data" + + */ + + function _onBlur(e, internal, two) { + + e.preventDefault(); + + e.stopPropagation(); + + + + var data = e.data; + + + + data.$selecter.removeClass("focus") + + .off("keydown.selecter-" + data.guid); + + + + $(".selecter").not(data.$selecter) + + .trigger("close.selecter", [ data ]); + + } + + + + /** + + * @method private + + * @name _onKeypress + + * @description Handles instance keypress, once focused + + * @param e [object] "Event data" + + */ + + function _onKeypress(e) { + + var data = e.data; + + + + if (e.keyCode === 13) { + + if (data.$selecter.hasClass("open")) { + + _onClose(e); + + _update(data.index, data); + + } + + _handleChange(data); + + } else if (e.keyCode !== 9 && (!e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)) { + + // Ignore modifiers & tabs + + e.preventDefault(); + + e.stopPropagation(); + + + + var total = data.$items.length - 1, + + index = (data.index < 0) ? 0 : data.index; + + + + // Firefox left/right support thanks to Kylemade + + if ($.inArray(e.keyCode, (isFirefox) ? [38, 40, 37, 39] : [38, 40]) > -1) { + + // Increment / decrement using the arrow keys + + index = index + ((e.keyCode === 38 || (isFirefox && e.keyCode === 37)) ? -1 : 1); + + + + if (index < 0) { + + index = 0; + + } + + if (index > total) { + + index = total; + + } + + } else { + + var input = String.fromCharCode(e.keyCode).toUpperCase(), + + letter, + + i; + + + + // Search for input from original index + + for (i = data.index + 1; i <= total; i++) { + + letter = data.$options.eq(i).text().charAt(0).toUpperCase(); + + if (letter === input) { + + index = i; + + break; + + } + + } + + + + // If not, start from the beginning + + if (index < 0 || index === data.index) { + + for (i = 0; i <= total; i++) { + + letter = data.$options.eq(i).text().charAt(0).toUpperCase(); + + if (letter === input) { + + index = i; + + break; + + } + + } + + } + + } + + + + // Update + + if (index >= 0) { + + _update(index, data); + + _scrollOptions(data); + + //trigger the change event on the select when updating the selecter + + data.$select.change() + + } + + } + + } + + + + /** + + * @method private + + * @name _update + + * @description Updates instance based on new target index + + * @param index [int] "Selected option index" + + * @param data [object] "instance data" + + */ + + function _update(index, data) { + + var $item = data.$items.eq(index), + + isSelected = $item.hasClass("selected"), + + isDisabled = $item.hasClass("disabled"); + + + + // Check for disabled options + + if (!isDisabled) { + + if (data.multiple) { + + if (isSelected) { + + data.$options.eq(index).prop("selected", null); + + $item.removeClass("selected"); + + } else { + + data.$options.eq(index).prop("selected", true); + + $item.addClass("selected"); + + } + + } else if (index > -1 && index < data.$items.length) { + + var newLabel = $item.html(), + + newValue = $item.data("value"); + + + + data.$selected.html(newLabel) + + .removeClass('placeholder'); + + + + data.$items.filter(".selected") + + .removeClass("selected"); + + + + data.$select[0].selectedIndex = index; + + + + $item.addClass("selected"); + + data.index = index; + + } else if (data.label !== "") { + + data.$selected.html(data.label); + + } + + } + + } + + + + /** + + * @method private + + * @name _scrollOptions + + * @description Scrolls options wrapper to specific option + + * @param data [object] "Instance data" + + */ + + function _scrollOptions(data) { + + var $selected = data.$items.eq(data.index), + + selectedOffset = (data.index >= 0 && !$selected.hasClass("placeholder")) ? $selected.position() : { left: 0, top: 0 }; + + + + if ($.fn.scroller !== undefined) { + + data.$itemsWrapper.scroller("scroll", (data.$itemsWrapper.find(".scroller-content").scrollTop() + selectedOffset.top), 0) + + .scroller("reset"); + + } else { + + data.$itemsWrapper.scrollTop( data.$itemsWrapper.scrollTop() + selectedOffset.top ); + + } + + } + + + + /** + + * @method private + + * @name _handleChange + + * @description Handles change events + + * @param data [object] "Instance data" + + */ + + function _handleChange(data) { + + if (data.links) { + + _launch(data); + + } else { + + data.callback.call(data.$selecter, data.$select.val(), data.index); + + data.$select.trigger("change", [ true ]); + + } + + } + + + + /** + + * @method private + + * @name _launch + + * @description Launches link + + * @param data [object] "Instance data" + + */ + + function _launch(data) { + + //var url = (isMobile) ? data.$select.val() : data.$options.filter(":selected").attr("href"); + + var url = data.$select.val(); + + + + if (data.external) { + + // Open link in a new tab/window + + window.open(url); + + } else { + + // Open link in same tab/window + + window.location.href = url; + + } + + } + + + + /** + + * @method private + + * @name _trim + + * @description Trims text, if specified length is greater then 0 + + * @param length [int] "Length to trim at" + + * @param text [string] "Text to trim" + + * @return [string] "Trimmed string" + + */ + + function _trim(text, length) { + + if (length === 0) { + + return text; + + } else { + + if (text.length > length) { + + return text.substring(0, length) + "..."; + + } else { + + return text; + + } + + } + + } + + + + /** + + * @method private + + * @name _escape + + * @description Escapes text + + * @param text [string] "Text to escape" + + */ + + function _escape(text) { + + return (typeof text === "string") ? text.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1') : text; + + } + + + + $.fn.selecter = function(method) { + + if (pub[method]) { + + return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); + + } else if (typeof method === 'object' || !method) { + + return _init.apply(this, arguments); + + } + + return this; + + }; + + + + $.selecter = function(method) { + + if (method === "defaults") { + + pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); + + } + + }; + +})(jQuery, window); + diff --git a/src/jquery.fs.selecter.js b/src/jquery.fs.selecter.js index 1df4cb9..7473c9e 100644 --- a/src/jquery.fs.selecter.js +++ b/src/jquery.fs.selecter.js @@ -698,6 +698,8 @@ if (index >= 0) { _update(index, data); _scrollOptions(data); + //trigger the change event on the select when updating the selecter + data.$select.change() } } } From 953a8974ad3d4cc3870b01f57ab344862795c172 Mon Sep 17 00:00:00 2001 From: spirosdi Date: Wed, 29 Oct 2014 16:54:48 +0100 Subject: [PATCH 2/3] removed whitespaces added by editor --- jquery.fs.selecter.js | 839 +----------------------------------------- 1 file changed, 1 insertion(+), 838 deletions(-) diff --git a/jquery.fs.selecter.js b/jquery.fs.selecter.js index dd33ce1..8d20e34 100644 --- a/jquery.fs.selecter.js +++ b/jquery.fs.selecter.js @@ -1,467 +1,235 @@ -/* - * Selecter v3.2.3 - 2014-10-24 - * A jQuery plugin for replacing default select elements. Part of the Formstone Library. - * http://formstone.it/selecter/ - * - * Copyright 2014 Ben Plum; MIT Licensed - */ - ;(function ($, window) { - "use strict"; - - var guid = 0, - userAgent = (window.navigator.userAgent||window.navigator.vendor||window.opera), - isFirefox = /Firefox/i.test(userAgent), - isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent), - isFirefoxMobile = (isFirefox && isMobile), - $body = null; - - /** - * @options - * @param callback [function] <$.noop> "Select item callback" - * @param cover [boolean] "Cover handle with option set" - * @param customClass [string] <''> "Class applied to instance" - * @param label [string] <''> "Label displayed before selection" - * @param external [boolean] "Open options as links in new window" - * @param links [boolean] "Open options as links in same window" - * @param mobile [boolean] "Force desktop interaction on mobile" - * @param trim [int] <0> "Trim options to specified length; 0 to disable” - */ - var options = { - callback: $.noop, - cover: false, - customClass: "", - label: "", - external: false, - links: false, - mobile: false, - trim: 0 - }; - - var pub = { - - /** - * @method - * @name defaults - * @description Sets default plugin options - * @param opts [object] <{}> "Options object" - * @example $.selecter("defaults", opts); - */ - defaults: function(opts) { - options = $.extend(options, opts || {}); - return $(this); - }, - - /** - * @method - * @name disable - * @description Disables target instance or option - * @param option [string] "Target option value" - * @example $(".target").selecter("disable", "1"); - */ - disable: function(option) { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (typeof option !== "undefined") { - var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); - - data.$items.eq(index).addClass("disabled"); - data.$options.eq(index).prop("disabled", true); - } else { - if (data.$selecter.hasClass("open")) { - data.$selecter.find(".selecter-selected").trigger("click.selecter"); - } - - data.$selecter.addClass("disabled"); - data.$select.prop("disabled", true); - } - } - }); - }, - - /** - * @method - * @name destroy - * @description Removes instance of plugin - * @example $(".target").selecter("destroy"); - */ - destroy: function() { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (data.$selecter.hasClass("open")) { - data.$selecter.find(".selecter-selected").trigger("click.selecter"); - } - - // Scroller support - if ($.fn.scroller !== undefined) { - data.$selecter.find(".selecter-options").scroller("destroy"); - } - - data.$select[0].tabIndex = data.tabIndex; - - data.$select.find(".selecter-placeholder").remove(); - data.$selected.remove(); - data.$itemsWrapper.remove(); - - data.$selecter.off(".selecter"); - - data.$select.off(".selecter") - .removeClass("selecter-element") - .show() - .unwrap(); - } - }); - }, - - /** - * @method - * @name enable - * @description Enables target instance or option - * @param option [string] "Target option value" - * @example $(".target").selecter("enable", "1"); - */ - enable: function(option) { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - if (typeof option !== "undefined") { - var index = data.$items.index( data.$items.filter("[data-value=" + option + "]") ); - data.$items.eq(index).removeClass("disabled"); - data.$options.eq(index).prop("disabled", false); - } else { - data.$selecter.removeClass("disabled"); - data.$select.prop("disabled", false); - } - } - }); - }, - - - /** - * @method private - * @name refresh - * @description DEPRECATED - Updates instance base on target options - * @example $(".target").selecter("refresh"); - */ - refresh: function() { - return pub.update.apply($(this)); - }, - - /** - * @method - * @name update - * @description Updates instance base on target options - * @example $(".target").selecter("update"); - */ - update: function() { - return $(this).each(function(i, input) { - var data = $(input).parent(".selecter").data("selecter"); - - if (data) { - var index = data.index; - - data.$allOptions = data.$select.find("option, optgroup"); - data.$options = data.$allOptions.filter("option"); - data.index = -1; - - index = data.$options.index(data.$options.filter(":selected")); - - _buildOptions(data); - - if (!data.multiple) { - _update(index, data); - } - } - }); - } - }; - - /** - * @method private - * @name _init - * @description Initializes plugin - * @param opts [object] "Initialization options" - */ - function _init(opts) { - // Local options - opts = $.extend({}, options, opts || {}); - - // Check for Body - if ($body === null) { - $body = $("body"); - } - - // Apply to each element - var $items = $(this); - for (var i = 0, count = $items.length; i < count; i++) { - _build($items.eq(i), opts); - } - return $items; - } - - /** - * @method private - * @name _build - * @description Builds each instance - * @param $select [jQuery object] "Target jQuery object" - * @param opts [object] <{}> "Options object" - */ - function _build($select, opts) { - if (!$select.hasClass("selecter-element")) { - // EXTEND OPTIONS - opts = $.extend({}, opts, $select.data("selecter-options")); - - opts.multiple = $select.prop("multiple"); - opts.disabled = $select.is(":disabled"); - - if (opts.external) { - opts.links = true; - } // Grab true original index, only if selected attribute exits var $originalOption = $select.find("[selected]").not(":disabled"), originalOptionIndex = $select.find("option").index($originalOption); - if (!opts.multiple && opts.label !== "") { - $select.prepend(''); if (originalOptionIndex > -1) { originalOptionIndex++; } - } else { - opts.label = ""; - } - - // Build options array - var $allOptions = $select.find("option, optgroup"), - $options = $allOptions.filter("option"); // If we didn't actually have a selected elemtn @@ -471,1212 +239,607 @@ // Determine original item var originalIndex = (originalOptionIndex > -1) ? originalOptionIndex : 0, - originalLabel = (opts.label !== "") ? opts.label : $originalOption.text(), - wrapperTag = "div"; - - // Swap tab index, no more interacting with the actual select! - opts.tabIndex = $select[0].tabIndex; - $select[0].tabIndex = -1; - - // Build HTML - var inner = "", - wrapper = ""; - - // Build wrapper - wrapper += '<' + wrapperTag + ' class="selecter ' + opts.customClass; - // Special case classes - if (isMobile) { - wrapper += ' mobile'; - } else if (opts.cover) { - wrapper += ' cover'; - } - if (opts.multiple) { - wrapper += ' multiple'; - } else { - wrapper += ' closed'; - } - if (opts.disabled) { - wrapper += ' disabled'; - } - wrapper += '" tabindex="' + opts.tabIndex + '">'; - wrapper += ''; - - // Build inner - if (!opts.multiple) { - inner += ''; - inner += $('').text( _trim(originalLabel, opts.trim) ).html(); - inner += ''; - } - inner += '
'; - inner += '
'; - - // Modify DOM - $select.addClass("selecter-element") - .wrap(wrapper) - .after(inner); - - // Store plugin data - var $selecter = $select.parent(".selecter"), - data = $.extend({ - $select: $select, - $allOptions: $allOptions, - $options: $options, - $selecter: $selecter, - $selected: $selecter.find(".selecter-selected"), - $itemsWrapper: $selecter.find(".selecter-options"), - index: -1, - guid: guid++ - }, opts); - - _buildOptions(data); - - if (!data.multiple) { - _update(originalIndex, data); - } - - // Scroller support - if ($.fn.scroller !== undefined) { - data.$itemsWrapper.scroller(); - } - - // Bind click events - data.$selecter.on("touchstart.selecter", ".selecter-selected", data, _onTouchStart) - .on("click.selecter", ".selecter-selected", data, _onClick) - .on("click.selecter", ".selecter-item", data, _onSelect) - .on("close.selecter", data, _onClose) - .data("selecter", data); - - // Change events - data.$select.on("change.selecter", data, _onChange); - - // Focus/Blur events - if (!isMobile) { - data.$selecter.on("focusin.selecter", data, _onFocus) - .on("blur.selecter", data, _onBlur); - - // Handle clicks to associated labels - data.$select.on("focusin.selecter", data, function(e) { - e.data.$selecter.trigger("focus"); - }); - } - } - } - - /** - * @method private - * @name _buildOptions - * @description Builds instance's option set - * @param data [object] "Instance data" - */ - function _buildOptions(data) { - var html = '', - itemTag = (data.links) ? "a" : "span", - j = 0; - - for (var i = 0, count = data.$allOptions.length; i < count; i++) { - var $op = data.$allOptions.eq(i); - - // Option group - if ($op[0].tagName === "OPTGROUP") { - html += ''; - } else { - var opVal = $op.val(); - - if (!$op.attr("value")) { - $op.attr("value", opVal); - } - - html += '<' + itemTag + ' class="selecter-item'; - if ($op.hasClass('selecter-placeholder')) { - html += ' placeholder'; - } - // Default selected value - now handles multi's thanks to @kuilkoff - if ($op.is(':selected')) { - html += ' selected'; - } - // Disabled options - if ($op.is(":disabled")) { - html += ' disabled'; - } - html += '" '; - if (data.links) { - html += 'href="' + opVal + '"'; - } else { - html += 'data-value="' + opVal + '"'; - } - html += '>' + $("").text( _trim($op.text(), data.trim) ).html() + ''; - j++; - } - } - - data.$itemsWrapper.html(html); - data.$items = data.$selecter.find(".selecter-item"); - } - - /** - * @method private - * @name _onTouchStart - * @description Handles touchstart to selected item - * @param e [object] "Event data" - */ - function _onTouchStart(e) { - e.stopPropagation(); - - var data = e.data; - - data.touchStartEvent = e.originalEvent; - - data.touchStartX = data.touchStartEvent.touches[0].clientX; - data.touchStartY = data.touchStartEvent.touches[0].clientY; - - data.$selecter.on("touchmove.selecter", ".selecter-selected", data, _onTouchMove) - .on("touchend.selecter", ".selecter-selected", data, _onTouchEnd); - } - - /** - * @method private - * @name _onTouchMove - * @description Handles touchmove to selected item - * @param e [object] "Event data" - */ - function _onTouchMove(e) { - var data = e.data, - oe = e.originalEvent; - - if (Math.abs(oe.touches[0].clientX - data.touchStartX) > 10 || Math.abs(oe.touches[0].clientY - data.touchStartY) > 10) { - data.$selecter.off("touchmove.selecter touchend.selecter"); - } - } - - /** - * @method private - * @name _onTouchEnd - * @description Handles touchend to selected item - * @param e [object] "Event data" - */ - function _onTouchEnd(e) { - var data = e.data; - - data.touchStartEvent.preventDefault(); - - data.$selecter.off("touchmove.selecter touchend.selecter"); - - _onClick(e); - } - - /** - * @method private - * @name _onClick - * @description Handles click to selected item - * @param e [object] "Event data" - */ - function _onClick(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - if (!data.$select.is(":disabled")) { - $(".selecter").not(data.$selecter).trigger("close.selecter", [data]); - - // Handle mobile, but not Firefox, unless desktop forced - if (!data.mobile && isMobile && !isFirefoxMobile) { - var el = data.$select[0]; - if (window.document.createEvent) { // All - var evt = window.document.createEvent("MouseEvents"); - evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - el.dispatchEvent(evt); - } else if (el.fireEvent) { // IE - el.fireEvent("onmousedown"); - } - } else { - // Delegate intent - if (data.$selecter.hasClass("closed")) { - _onOpen(e); - } else if (data.$selecter.hasClass("open")) { - _onClose(e); - } - } - } - } - - /** - * @method private - * @name _onOpen - * @description Opens option set - * @param e [object] "Event data" - */ - function _onOpen(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - // Make sure it's not alerady open - if (!data.$selecter.hasClass("open")) { - var offset = data.$selecter.offset(), - bodyHeight = $body.outerHeight(), - optionsHeight = data.$itemsWrapper.outerHeight(true), - selectedOffset = (data.index >= 0) ? data.$items.eq(data.index).position() : { left: 0, top: 0 }; - - // Calculate bottom of document - if (offset.top + optionsHeight > bodyHeight) { - data.$selecter.addClass("bottom"); - } - - data.$itemsWrapper.show(); - - // Bind Events - data.$selecter.removeClass("closed") - .addClass("open"); - $body.on("click.selecter-" + data.guid, ":not(.selecter-options)", data, _onCloseHelper); - - _scrollOptions(data); - } - } - - /** - * @method private - * @name _onCloseHelper - * @description Determines if event target is outside instance before closing - * @param e [object] "Event data" - */ - function _onCloseHelper(e) { - e.preventDefault(); - e.stopPropagation(); - - if ($(e.currentTarget).parents(".selecter").length === 0) { - _onClose(e); - } - } - - /** - * @method private - * @name _onClose - * @description Closes option set - * @param e [object] "Event data" - */ - function _onClose(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - // Make sure it's actually open - if (data.$selecter.hasClass("open")) { - data.$itemsWrapper.hide(); - data.$selecter.removeClass("open bottom") - .addClass("closed"); - - $body.off(".selecter-" + data.guid); - } - } - - /** - * @method private - * @name _onSelect - * @description Handles option select - * @param e [object] "Event data" - */ - function _onSelect(e) { - e.preventDefault(); - e.stopPropagation(); - - var $target = $(this), - data = e.data; - - if (!data.$select.is(":disabled")) { - if (data.$itemsWrapper.is(":visible")) { - // Update - var index = data.$items.index($target); - - if (index !== data.index) { - _update(index, data); - _handleChange(data); - } - } - - if (!data.multiple) { - // Clean up - _onClose(e); - } - } - } - - /** - * @method private - * @name _onChange - * @description Handles external changes - * @param e [object] "Event data" - */ - function _onChange(e, internal) { - var $target = $(this), - - data = e.data; - - + data = e.data; if (!internal && !data.multiple) { - var index = data.$options.index(data.$options.filter("[value='" + _escape($target.val()) + "']")); - - _update(index, data); - _handleChange(data); - } - } - - /** - * @method private - * @name _onFocus - * @description Handles instance focus - * @param e [object] "Event data" - */ - function _onFocus(e) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - if (!data.$select.is(":disabled") && !data.multiple) { - data.$selecter.addClass("focus") - .on("keydown.selecter-" + data.guid, data, _onKeypress); - - $(".selecter").not(data.$selecter) - .trigger("close.selecter", [ data ]); - } - } - - /** - * @method private - * @name _onBlur - * @description Handles instance focus - * @param e [object] "Event data" - */ - function _onBlur(e, internal, two) { - e.preventDefault(); - e.stopPropagation(); - - var data = e.data; - - data.$selecter.removeClass("focus") - .off("keydown.selecter-" + data.guid); - - $(".selecter").not(data.$selecter) - .trigger("close.selecter", [ data ]); - } - - /** - * @method private - * @name _onKeypress - * @description Handles instance keypress, once focused - * @param e [object] "Event data" - */ - function _onKeypress(e) { - var data = e.data; - - if (e.keyCode === 13) { - if (data.$selecter.hasClass("open")) { - _onClose(e); - _update(data.index, data); - } - _handleChange(data); - } else if (e.keyCode !== 9 && (!e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)) { - // Ignore modifiers & tabs - e.preventDefault(); - e.stopPropagation(); - - var total = data.$items.length - 1, - index = (data.index < 0) ? 0 : data.index; - - // Firefox left/right support thanks to Kylemade - if ($.inArray(e.keyCode, (isFirefox) ? [38, 40, 37, 39] : [38, 40]) > -1) { - // Increment / decrement using the arrow keys - index = index + ((e.keyCode === 38 || (isFirefox && e.keyCode === 37)) ? -1 : 1); - - if (index < 0) { - index = 0; - } - if (index > total) { - index = total; - } - } else { - var input = String.fromCharCode(e.keyCode).toUpperCase(), - letter, - i; - - // Search for input from original index - for (i = data.index + 1; i <= total; i++) { - letter = data.$options.eq(i).text().charAt(0).toUpperCase(); - if (letter === input) { - index = i; - break; - } - } - - // If not, start from the beginning - if (index < 0 || index === data.index) { - for (i = 0; i <= total; i++) { - letter = data.$options.eq(i).text().charAt(0).toUpperCase(); - if (letter === input) { - index = i; - break; - } - } - } - } - - // Update - if (index >= 0) { - _update(index, data); - _scrollOptions(data); - //trigger the change event on the select when updating the selecter - data.$select.change() - } - } - } - - /** - * @method private - * @name _update - * @description Updates instance based on new target index - * @param index [int] "Selected option index" - * @param data [object] "instance data" - */ - function _update(index, data) { - var $item = data.$items.eq(index), - isSelected = $item.hasClass("selected"), - isDisabled = $item.hasClass("disabled"); - - // Check for disabled options - if (!isDisabled) { - if (data.multiple) { - if (isSelected) { - data.$options.eq(index).prop("selected", null); - $item.removeClass("selected"); - } else { - data.$options.eq(index).prop("selected", true); - $item.addClass("selected"); - } - } else if (index > -1 && index < data.$items.length) { - var newLabel = $item.html(), - newValue = $item.data("value"); - - data.$selected.html(newLabel) - .removeClass('placeholder'); - - data.$items.filter(".selected") - .removeClass("selected"); - - data.$select[0].selectedIndex = index; - - $item.addClass("selected"); - data.index = index; - } else if (data.label !== "") { - data.$selected.html(data.label); - } - } - } - - /** - * @method private - * @name _scrollOptions - * @description Scrolls options wrapper to specific option - * @param data [object] "Instance data" - */ - function _scrollOptions(data) { - var $selected = data.$items.eq(data.index), - selectedOffset = (data.index >= 0 && !$selected.hasClass("placeholder")) ? $selected.position() : { left: 0, top: 0 }; - - if ($.fn.scroller !== undefined) { - data.$itemsWrapper.scroller("scroll", (data.$itemsWrapper.find(".scroller-content").scrollTop() + selectedOffset.top), 0) - .scroller("reset"); - } else { - data.$itemsWrapper.scrollTop( data.$itemsWrapper.scrollTop() + selectedOffset.top ); - } - } - - /** - * @method private - * @name _handleChange - * @description Handles change events - * @param data [object] "Instance data" - */ - function _handleChange(data) { - if (data.links) { - _launch(data); - } else { - data.callback.call(data.$selecter, data.$select.val(), data.index); - data.$select.trigger("change", [ true ]); - } - } - - /** - * @method private - * @name _launch - * @description Launches link - * @param data [object] "Instance data" - */ - function _launch(data) { - //var url = (isMobile) ? data.$select.val() : data.$options.filter(":selected").attr("href"); - var url = data.$select.val(); - - if (data.external) { - // Open link in a new tab/window - window.open(url); - } else { - // Open link in same tab/window - window.location.href = url; - } - } - - /** - * @method private - * @name _trim - * @description Trims text, if specified length is greater then 0 - * @param length [int] "Length to trim at" - * @param text [string] "Text to trim" - * @return [string] "Trimmed string" - */ - function _trim(text, length) { - if (length === 0) { - return text; - } else { - if (text.length > length) { - return text.substring(0, length) + "..."; - } else { - return text; - } - } - } - - /** - * @method private - * @name _escape - * @description Escapes text - * @param text [string] "Text to escape" - */ - function _escape(text) { - return (typeof text === "string") ? text.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1') : text; - } - - $.fn.selecter = function(method) { - if (pub[method]) { - return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method) { - return _init.apply(this, arguments); - } - return this; - }; - - $.selecter = function(method) { - if (method === "defaults") { - pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); - } - }; - })(jQuery, window); - From 51b282dfe70b9781f97c944fcd89c4ae012243ab Mon Sep 17 00:00:00 2001 From: spirosdi Date: Thu, 30 Oct 2014 14:35:39 +0100 Subject: [PATCH 3/3] Select option on pressing "tab" along with "enter" --- jquery.fs.selecter.js | 2 +- src/jquery.fs.selecter.js | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/jquery.fs.selecter.js b/jquery.fs.selecter.js index 8d20e34..80b3322 100644 --- a/jquery.fs.selecter.js +++ b/jquery.fs.selecter.js @@ -643,7 +643,7 @@ function _onKeypress(e) { var data = e.data; - if (e.keyCode === 13) { + if (e.keyCode === 13 || e.keyCode === 9) { if (data.$selecter.hasClass("open")) { _onClose(e); _update(data.index, data); diff --git a/src/jquery.fs.selecter.js b/src/jquery.fs.selecter.js index 7473c9e..3475fa1 100644 --- a/src/jquery.fs.selecter.js +++ b/src/jquery.fs.selecter.js @@ -213,16 +213,16 @@ if (opts.external) { opts.links = true; - } - - // Grab true original index, only if selected attribute exits - var $originalOption = $select.find("[selected]").not(":disabled"), - originalOptionIndex = $select.find("option").index($originalOption); + } + + // Grab true original index, only if selected attribute exits + var $originalOption = $select.find("[selected]").not(":disabled"), + originalOptionIndex = $select.find("option").index($originalOption); if (!opts.multiple && opts.label !== "") { - $select.prepend(''); - if (originalOptionIndex > -1) { - originalOptionIndex++; + $select.prepend(''); + if (originalOptionIndex > -1) { + originalOptionIndex++; } } else { opts.label = ""; @@ -230,14 +230,14 @@ // Build options array var $allOptions = $select.find("option, optgroup"), - $options = $allOptions.filter("option"); - - // If we didn't actually have a selected elemtn - if (!$originalOption.length) { - $originalOption = $options.eq(0); - } - - // Determine original item + $options = $allOptions.filter("option"); + + // If we didn't actually have a selected elemtn + if (!$originalOption.length) { + $originalOption = $options.eq(0); + } + + // Determine original item var originalIndex = (originalOptionIndex > -1) ? originalOptionIndex : 0, originalLabel = (opts.label !== "") ? opts.label : $originalOption.text(), wrapperTag = "div"; @@ -643,7 +643,7 @@ function _onKeypress(e) { var data = e.data; - if (e.keyCode === 13) { + if (e.keyCode === 13 || e.keyCode === 9) { if (data.$selecter.hasClass("open")) { _onClose(e); _update(data.index, data);