/** * @plugin * @name Core * @description Formstone Library core. Required for all plugins. */ var Formstone = this.Formstone = (function ($, window, document, undefined) { /* global ga */ "use strict"; // Namespace var Core = function() { this.Plugins = {}; this.ResizeHandlers = []; // Globals this.window = window; this.$window = $(window); this.document = document; this.$document = $(document); this.$body = null; this.windowWidth = 0; this.windowHeight = 0; this.userAgent = window.navigator.userAgent || window.navigator.vendor || window.opera; this.isFirefox = /Firefox/i.test(this.userAgent); this.isChrome = /Chrome/i.test(this.userAgent); this.isSafari = /Safari/i.test(this.userAgent) && !this.isChrome; this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test( this.userAgent ); this.isFirefoxMobile = (this.isFirefox && this.isMobile); this.transform = null; this.transition = null; this.support = { file : !!(window.File && window.FileList && window.FileReader), history : !!(window.history && window.history.pushState && window.history.replaceState), matchMedia : !!(window.matchMedia || window.msMatchMedia), raf : !!(window.requestAnimationFrame && window.cancelAnimationFrame), touch : !!(("ontouchstart" in window) || window.DocumentTouch && document instanceof window.DocumentTouch), transition : false, transform : false }; }, Functions = { /** * @method private * @name killEvent * @description Stops event action and bubble. * @param e [object] "Event data" */ killEvent: function(e, immediate) { try { e.preventDefault(); e.stopPropagation(); if (immediate) { e.stopImmediatePropagation(); } } catch(error) { // } }, /** * @method private * @name startTimer * @description Starts an internal timer. * @param timer [int] "Timer ID" * @param time [int] "Time until execution" * @param callback [function] "Function to execute" * @return [int] "Timer ID" */ startTimer: function(timer, time, callback, interval) { Functions.clearTimer(timer); return (interval) ? setInterval(callback, time) : setTimeout(callback, time); }, /** * @method private * @name clearTimer * @description Clears an internal timer. * @param timer [int] "Timer ID" */ clearTimer: function(timer, interval) { if (timer) { if (interval) { clearInterval(timer); } else { clearTimeout(timer); } timer = null; } }, /** * @method private * @name sortAsc * @description Sorts an array (ascending). * @param a [mixed] "First value" * @param b [mixed] "Second value" * @return Difference between second and first values */ sortAsc: function(a, b) { return (parseInt(b) - parseInt(a)); }, /** * @method private * @name sortDesc * @description Sorts an array (descending). * @param a [mixed] "First value" * @param b [mixed] "Second value" * @return Difference between second and first values */ sortDesc: function(a, b) { return (parseInt(b) - parseInt(a)); } }, Formstone = new Core(), // Classes Classes = { base : "{ns}", element : "{ns}-element" }, // Events Events = { namespace : ".{ns}", blur : "blur.{ns}", change : "change.{ns}", click : "click.{ns}", dblClick : "dblclick.{ns}", drag : "drag.{ns}", dragEnd : "dragend.{ns}", dragEnter : "dragenter.{ns}", dragLeave : "dragleave.{ns}", dragOver : "dragover.{ns}", dragStart : "dragstart.{ns}", drop : "drop.{ns}", error : "error.{ns}", focus : "focus.{ns}", focusIn : "focusin.{ns}", focusOut : "focusout.{ns}", input : "input.{ns}", keyDown : "keydown.{ns}", keyPress : "keypress.{ns}", keyUp : "keyup.{ns}", load : "load.{ns}", mouseDown : "mousedown.{ns}", mouseEnter : "mouseenter.{ns}", mouseLeave : "mouseleave.{ns}", mouseMove : "mousemove.{ns}", mouseOut : "mouseout.{ns}", mouseOver : "mouseover.{ns}", mouseUp : "mouseup.{ns}", resize : "resize.{ns}", scroll : "scroll.{ns}", select : "select.{ns}", touchCancel : "touchcancel.{ns}", touchEnd : "touchend.{ns}", touchLeave : "touchleave.{ns}", touchMove : "touchmove.{ns}", touchStart : "touchstart.{ns}" }; /** * @method * @name Plugin * @description Builds a plugin and registers it with jQuery. * @param namespace [string] "Plugin namespace" * @param settings [object] "Plugin settings" * @return [object] "Plugin properties. Includes `defaults`, `classes`, `events`, `functions`, `methods` and `utilities` keys" * @example Formstone.Plugin("namespace", { ... }); */ Core.prototype.Plugin = function(namespace, settings) { Formstone.Plugins[namespace] = (function(namespace, settings) { var namespaceDash = "fs-" + namespace, namespaceDot = "fs." + namespace; /** * @method private * @name initialize * @description Creates plugin instance by adding base classname, creating data and scoping a _construct call. * @param options [object] <{}> "Instance options" */ function initialize(options) { // Extend Defaults var hasOptions = $.type(options) === "object"; options = $.extend(true, {}, settings.defaults || {}, (hasOptions ? options : {})); // Maintain Chain var $targets = this; for (var i = 0, count = $targets.length; i < count; i++) { var $element = $targets.eq(i); // Gaurd Against Exiting Instances if (!getData($element)) { // Extend w/ Local Options var localOptions = $element.data(namespace + "-options"), data = $.extend(true, { $el : $element }, options, ($.type(localOptions) === "object" ? localOptions : {}) ); // Cache Instance $element.addClass(settings.classes.raw.element) .data(namespaceDash, data); // Constructor settings.methods._construct.apply($element, [ data ].concat(Array.prototype.slice.call(arguments, (hasOptions ? 1 : 0) ))); } } return $targets; } /** * @method private * @name destroy * @description Removes plugin instance by scoping a _destruct call, and removing the base classname and data. * @param data [object] <{}> "Instance data" */ /** * @method widget * @name destroy * @description Removes plugin instance. * @example $(".target").{ns}("destroy"); */ function destroy(data) { settings.functions.iterate.apply(this, [ settings.methods._destruct ].concat(Array.prototype.slice.call(arguments, 1))); this.removeClass(settings.classes.raw.element) .removeData(namespaceDash); } /** * @method private * @name getData * @description Creates class selector from text. * @param $element [jQuery] "Target jQuery object" * @return [object] "Instance data" */ function getData($element) { return $element.data(namespaceDash); } /** * @method private * @name delegateWidget * @description Delegates public methods. * @param method [string] "Method to execute" * @return [jQuery] "jQuery object" */ function delegateWidget(method) { // If jQuery object if (this instanceof $) { var _method = settings.methods[method]; // Public method OR false if ($.type(method) === "object" || !method) { // Initialize return initialize.apply(this, arguments); } else if (_method && method.indexOf("_") !== 0) { // Wrap Public Methods return settings.functions.iterate.apply(this, [ _method ].concat(Array.prototype.slice.call(arguments, 1))); } return this; } } /** * @method private * @name delegateUtility * @description Delegates utility methods. * @param method [string] "Method to execute" */ function delegateUtility(method) { // public utility OR utility init OR false var _method = settings.utilities[method] || settings.utilities._initialize || false; if (_method) { // Wrap Utility Methods return _method.apply(window, Array.prototype.slice.call(arguments, ($.type(method) === "object" ? 0 : 1) )); } } /** * @method utility * @name defaults * @description Extends plugin default settings; effects instances created hereafter. * @param options [object] <{}> "New plugin defaults" * @example $.{ns}("defaults", { ... }); */ function defaults(options) { settings.defaults = $.extend(true, settings.defaults, options || {}); } /** * @method private * @name iterate * @description Loops scoped function calls over jQuery object with instance data as first parameter. * @param func [function] "Function to execute" * @return [jQuery] "jQuery object" */ function iterate(fn) { var $targets = this; for (var i = 0, count = $targets.length; i < count; i++) { var $element = $targets.eq(i), data = getData($element) || {}; if ($.type(data.$el) !== "undefined") { fn.apply($element, [ data ].concat(Array.prototype.slice.call(arguments, 1))); } } return $targets; } // Locals settings.initialized = false; settings.priority = settings.priority || 10; // Namespace Classes & Events settings.classes = namespaceProperties("classes", namespaceDash, Classes, settings.classes); settings.events = namespaceProperties("events", namespace, Events, settings.events); // Extend Functions settings.functions = $.extend({ getData : getData, iterate : iterate }, Functions, settings.functions); // Extend Methods settings.methods = $.extend(true, { // Private Methods _setup : $.noop, // Document ready _construct : $.noop, // Constructor _destruct : $.noop, // Destructor _resize : false, // Window resize // Public Methods destroy : destroy }, settings.methods); // Extend Utilities settings.utilities = $.extend(true, { // Private Utilities _initialize : false, // First Run _delegate : false, // Custom Delegation // Public Utilities defaults : defaults }, settings.utilities); // Register Plugin // Widget if (settings.widget) { // Widget Delegation: $(".target").plugin("method", ...); $.fn[namespace] = delegateWidget; } // Utility // Utility Delegation: $.plugin("method", ... ); $[namespace] = settings.utilities._delegate || delegateUtility; // Run Setup settings.namespace = namespace; // Resize handler if (settings.methods._resize) { Formstone.ResizeHandlers.push({ namespace: namespace, priority: settings.priority, callback: settings.methods._resize }); // Sort handlers on push Formstone.ResizeHandlers.sort(sortPriority); } return settings; })(namespace, settings); return Formstone.Plugins[namespace]; }; // Namespace Properties function namespaceProperties(type, namespace, globalProps, customProps) { var _props = { raw: {} }, i; customProps = customProps || {}; for (i in customProps) { if (customProps.hasOwnProperty(i)) { if (type === "classes") { // Custom classes _props.raw[ customProps[i] ] = namespace + "-" + customProps[i]; _props[ customProps[i] ] = "." + namespace + "-" + customProps[i]; } else { // Custom events _props.raw[ i ] = customProps[i]; _props[ i ] = customProps[i] + "." + namespace; } } } for (i in globalProps) { if (globalProps.hasOwnProperty(i)) { if (type === "classes") { // Global classes _props.raw[ i ] = globalProps[i].replace(/{ns}/g, namespace); _props[ i ] = globalProps[i].replace(/{ns}/g, "." + namespace); } else { // Global events _props.raw[ i ] = globalProps[i].replace(/.{ns}/g, ""); _props[ i ] = globalProps[i].replace(/{ns}/g, namespace); } } } return _props; } // Set Transition Information function setTransitionInformation() { var transitionEvents = { "transition" : "transitionend", "MozTransition" : "transitionend", "OTransition" : "otransitionend", "WebkitTransition" : "webkitTransitionEnd" }, transitionProperties = [ "transition", "-webkit-transition" ], transformProperties = { 'transform' : 'transform', 'MozTransform' : '-moz-transform', 'OTransform' : '-o-transform', 'msTransform' : '-ms-transform', 'webkitTransform' : '-webkit-transform' }, transitionEvent = "transitionend", transitionProperty = "", transformProperty = "", test = document.createElement("div"), i; for (i in transitionEvents) { if (transitionEvents.hasOwnProperty(i) && i in test.style) { transitionEvent = transitionEvents[i]; Formstone.support.transition = true; break; } } Events.transitionEnd = transitionEvent + ".{ns}"; for (i in transitionProperties) { if (transitionProperties.hasOwnProperty(i) && transitionProperties[i] in test.style) { transitionProperty = transitionProperties[i]; break; } } Formstone.transition = transitionProperty; for (i in transformProperties) { if (transformProperties.hasOwnProperty(i) && transformProperties[i] in test.style) { Formstone.support.transform = true; transformProperty = transformProperties[i]; break; } } Formstone.transform = transformProperty; } // Window resize var ResizeTimer = null, Debounce = 20; function onWindowResize() { Formstone.windowWidth = Formstone.$window.width(); Formstone.windowHeight = Formstone.$window.height(); ResizeTimer = Functions.startTimer(ResizeTimer, Debounce, handleWindowResize); } function handleWindowResize() { for (var i in Formstone.ResizeHandlers) { if (Formstone.ResizeHandlers.hasOwnProperty(i)) { Formstone.ResizeHandlers[i].callback.call(window, Formstone.windowWidth, Formstone.windowHeight); } } } Formstone.$window.on("resize.fs", onWindowResize); onWindowResize(); // Sort Priority function sortPriority(a, b) { return (parseInt(a.priority) - parseInt(b.priority)); } // Document Ready $(function() { Formstone.$body = $("body"); for (var i in Formstone.Plugins) { if (Formstone.Plugins.hasOwnProperty(i) && !Formstone.Plugins[i].initialized) { Formstone.Plugins[i].methods._setup.call(document); Formstone.Plugins[i].initialized = true; } } }); // Custom Events Events.clickTouchStart = Events.click + " " + Events.touchStart; // Transitions setTransitionInformation(); return Formstone; })(jQuery, this, document);